Merge "Add UI integration tests using shim tools" into androidx-main
diff --git a/.github/actions/build-single-project/action.yml b/.github/actions/build-single-project/action.yml
index 8c7af75..ecedb70 100644
--- a/.github/actions/build-single-project/action.yml
+++ b/.github/actions/build-single-project/action.yml
@@ -32,8 +32,10 @@
shell: bash
run: echo "yes" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --install "cmake;3.22.1"
- name: "Install NDK"
+ working-directory: ${{ github.workspace }}
shell: bash
run: |
+ set -x
NDK_VERSION=$(grep "ndkVersion" settings.gradle | awk -F "=" '{gsub(/"| /, ""); print $2}')
echo "yes" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --install "ndk;$NDK_VERSION"
- name: "Install Android SDK Build-Tools"
diff --git a/OWNERS b/OWNERS
index 0bbe0a2..63cb0b9 100644
--- a/OWNERS
+++ b/OWNERS
@@ -39,6 +39,8 @@
per-file *libraryversions.toml = [email protected]
# Glance
per-file *libraryversions.toml = [email protected]
+# AppSearch
+per-file *libraryversions.toml = [email protected], [email protected], [email protected]
# Copybara can self-approve CLs within synced docs.
per-file docs/** = [email protected]
\ No newline at end of file
diff --git a/activity/activity-compose/api/1.10.0-beta01.txt b/activity/activity-compose/api/1.10.0-beta01.txt
deleted file mode 100644
index df9fcaf..0000000
--- a/activity/activity-compose/api/1.10.0-beta01.txt
+++ /dev/null
@@ -1,55 +0,0 @@
-// Signature format: 4.0
-package androidx.activity.compose {
-
- public final class ActivityResultRegistryKt {
- method @androidx.compose.runtime.Composable public static <I, O> androidx.activity.compose.ManagedActivityResultLauncher<I,O> rememberLauncherForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O> contract, kotlin.jvm.functions.Function1<? super O,kotlin.Unit> onResult);
- }
-
- public final class BackHandlerKt {
- method @androidx.compose.runtime.Composable public static void BackHandler(optional boolean enabled, kotlin.jvm.functions.Function0<kotlin.Unit> onBack);
- }
-
- public final class ComponentActivityKt {
- method public static void setContent(androidx.activity.ComponentActivity, optional androidx.compose.runtime.CompositionContext? parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- }
-
- public final class LocalActivityResultRegistryOwner {
- method @androidx.compose.runtime.Composable public androidx.activity.result.ActivityResultRegistryOwner? getCurrent();
- method public infix androidx.compose.runtime.ProvidedValue<androidx.activity.result.ActivityResultRegistryOwner?> provides(androidx.activity.result.ActivityResultRegistryOwner registryOwner);
- property @androidx.compose.runtime.Composable public final androidx.activity.result.ActivityResultRegistryOwner? current;
- field public static final androidx.activity.compose.LocalActivityResultRegistryOwner INSTANCE;
- }
-
- public final class LocalFullyDrawnReporterOwner {
- method @androidx.compose.runtime.Composable public androidx.activity.FullyDrawnReporterOwner? getCurrent();
- method public infix androidx.compose.runtime.ProvidedValue<androidx.activity.FullyDrawnReporterOwner?> provides(androidx.activity.FullyDrawnReporterOwner fullyDrawnReporterOwner);
- property @androidx.compose.runtime.Composable public final androidx.activity.FullyDrawnReporterOwner? current;
- field public static final androidx.activity.compose.LocalFullyDrawnReporterOwner INSTANCE;
- }
-
- public final class LocalOnBackPressedDispatcherOwner {
- method @androidx.compose.runtime.Composable public androidx.activity.OnBackPressedDispatcherOwner? getCurrent();
- method public infix androidx.compose.runtime.ProvidedValue<androidx.activity.OnBackPressedDispatcherOwner?> provides(androidx.activity.OnBackPressedDispatcherOwner dispatcherOwner);
- property @androidx.compose.runtime.Composable public final androidx.activity.OnBackPressedDispatcherOwner? current;
- field public static final androidx.activity.compose.LocalOnBackPressedDispatcherOwner INSTANCE;
- }
-
- public final class ManagedActivityResultLauncher<I, O> extends androidx.activity.result.ActivityResultLauncher<I> {
- method public androidx.activity.result.contract.ActivityResultContract<I,O> getContract();
- method public void launch(I input, androidx.core.app.ActivityOptionsCompat? options);
- method @Deprecated public void unregister();
- property public androidx.activity.result.contract.ActivityResultContract<I,O> contract;
- }
-
- public final class PredictiveBackHandlerKt {
- method @androidx.compose.runtime.Composable public static void PredictiveBackHandler(optional boolean enabled, kotlin.jvm.functions.Function2<kotlinx.coroutines.flow.Flow<androidx.activity.BackEventCompat>,? super kotlin.coroutines.Continuation<kotlin.Unit>,? extends java.lang.Object?> onBack);
- }
-
- public final class ReportDrawnKt {
- method @androidx.compose.runtime.Composable public static void ReportDrawn();
- method @androidx.compose.runtime.Composable public static void ReportDrawnAfter(kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block);
- method @androidx.compose.runtime.Composable public static void ReportDrawnWhen(kotlin.jvm.functions.Function0<java.lang.Boolean> predicate);
- }
-
-}
-
diff --git a/activity/activity-compose/api/restricted_1.10.0-beta01.txt b/activity/activity-compose/api/restricted_1.10.0-beta01.txt
deleted file mode 100644
index df9fcaf..0000000
--- a/activity/activity-compose/api/restricted_1.10.0-beta01.txt
+++ /dev/null
@@ -1,55 +0,0 @@
-// Signature format: 4.0
-package androidx.activity.compose {
-
- public final class ActivityResultRegistryKt {
- method @androidx.compose.runtime.Composable public static <I, O> androidx.activity.compose.ManagedActivityResultLauncher<I,O> rememberLauncherForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O> contract, kotlin.jvm.functions.Function1<? super O,kotlin.Unit> onResult);
- }
-
- public final class BackHandlerKt {
- method @androidx.compose.runtime.Composable public static void BackHandler(optional boolean enabled, kotlin.jvm.functions.Function0<kotlin.Unit> onBack);
- }
-
- public final class ComponentActivityKt {
- method public static void setContent(androidx.activity.ComponentActivity, optional androidx.compose.runtime.CompositionContext? parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- }
-
- public final class LocalActivityResultRegistryOwner {
- method @androidx.compose.runtime.Composable public androidx.activity.result.ActivityResultRegistryOwner? getCurrent();
- method public infix androidx.compose.runtime.ProvidedValue<androidx.activity.result.ActivityResultRegistryOwner?> provides(androidx.activity.result.ActivityResultRegistryOwner registryOwner);
- property @androidx.compose.runtime.Composable public final androidx.activity.result.ActivityResultRegistryOwner? current;
- field public static final androidx.activity.compose.LocalActivityResultRegistryOwner INSTANCE;
- }
-
- public final class LocalFullyDrawnReporterOwner {
- method @androidx.compose.runtime.Composable public androidx.activity.FullyDrawnReporterOwner? getCurrent();
- method public infix androidx.compose.runtime.ProvidedValue<androidx.activity.FullyDrawnReporterOwner?> provides(androidx.activity.FullyDrawnReporterOwner fullyDrawnReporterOwner);
- property @androidx.compose.runtime.Composable public final androidx.activity.FullyDrawnReporterOwner? current;
- field public static final androidx.activity.compose.LocalFullyDrawnReporterOwner INSTANCE;
- }
-
- public final class LocalOnBackPressedDispatcherOwner {
- method @androidx.compose.runtime.Composable public androidx.activity.OnBackPressedDispatcherOwner? getCurrent();
- method public infix androidx.compose.runtime.ProvidedValue<androidx.activity.OnBackPressedDispatcherOwner?> provides(androidx.activity.OnBackPressedDispatcherOwner dispatcherOwner);
- property @androidx.compose.runtime.Composable public final androidx.activity.OnBackPressedDispatcherOwner? current;
- field public static final androidx.activity.compose.LocalOnBackPressedDispatcherOwner INSTANCE;
- }
-
- public final class ManagedActivityResultLauncher<I, O> extends androidx.activity.result.ActivityResultLauncher<I> {
- method public androidx.activity.result.contract.ActivityResultContract<I,O> getContract();
- method public void launch(I input, androidx.core.app.ActivityOptionsCompat? options);
- method @Deprecated public void unregister();
- property public androidx.activity.result.contract.ActivityResultContract<I,O> contract;
- }
-
- public final class PredictiveBackHandlerKt {
- method @androidx.compose.runtime.Composable public static void PredictiveBackHandler(optional boolean enabled, kotlin.jvm.functions.Function2<kotlinx.coroutines.flow.Flow<androidx.activity.BackEventCompat>,? super kotlin.coroutines.Continuation<kotlin.Unit>,? extends java.lang.Object?> onBack);
- }
-
- public final class ReportDrawnKt {
- method @androidx.compose.runtime.Composable public static void ReportDrawn();
- method @androidx.compose.runtime.Composable public static void ReportDrawnAfter(kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block);
- method @androidx.compose.runtime.Composable public static void ReportDrawnWhen(kotlin.jvm.functions.Function0<java.lang.Boolean> predicate);
- }
-
-}
-
diff --git a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/PredictiveBackHandlerTest.kt b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/PredictiveBackHandlerTest.kt
index 1ba51b4..a8e1bee 100644
--- a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/PredictiveBackHandlerTest.kt
+++ b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/PredictiveBackHandlerTest.kt
@@ -172,6 +172,71 @@
}
}
+ @Test
+ fun testPredictiveBackHandlerDisabledBeforeStart() {
+ val result = mutableListOf<String>()
+ var count by mutableStateOf(2)
+ lateinit var dispatcherOwner: TestOnBackPressedDispatcherOwner
+ lateinit var dispatcher: OnBackPressedDispatcher
+ var started = false
+
+ rule.setContent {
+ dispatcherOwner =
+ TestOnBackPressedDispatcherOwner(LocalLifecycleOwner.current.lifecycle)
+ CompositionLocalProvider(LocalOnBackPressedDispatcherOwner provides dispatcherOwner) {
+ PredictiveBackHandler(count > 1) { progress ->
+ if (count <= 1) {
+ started = true
+ }
+ progress.collect()
+ result += "onBack"
+ }
+ dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
+ }
+ }
+
+ // Changing the count right before starting the gesture is received in the
+ // onBackStackStarted callback
+ count = 1
+ dispatcher.startGestureBack()
+
+ rule.runOnIdle { assertThat(started).isTrue() }
+ dispatcher.api34Complete()
+ rule.runOnIdle { assertThat(result).isEqualTo(listOf("onBack")) }
+ }
+
+ fun testPredictiveBackHandlerDisabledAfterStart() {
+ val result = mutableListOf<String>()
+ var count by mutableStateOf(2)
+ lateinit var dispatcherOwner: TestOnBackPressedDispatcherOwner
+ lateinit var dispatcher: OnBackPressedDispatcher
+ var started = false
+
+ rule.setContent {
+ dispatcherOwner =
+ TestOnBackPressedDispatcherOwner(LocalLifecycleOwner.current.lifecycle)
+ CompositionLocalProvider(LocalOnBackPressedDispatcherOwner provides dispatcherOwner) {
+ PredictiveBackHandler(count > 1) { progress ->
+ if (count <= 1) {
+ started = true
+ }
+ progress.collect()
+ result += "onBack"
+ }
+ dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
+ }
+ }
+
+ dispatcher.startGestureBack()
+ // Changing the count right after starting the gesture is not received in the
+ // onBackStackStarted callback
+ count = 1
+
+ rule.runOnIdle { assertThat(started).isFalse() }
+ dispatcher.api34Complete()
+ rule.runOnIdle { assertThat(result).isEqualTo(listOf("onBack")) }
+ }
+
@Test(expected = IllegalStateException::class)
fun testNoCollection() {
val result = mutableListOf<String>()
diff --git a/activity/activity-compose/src/main/java/androidx/activity/compose/PredictiveBackHandler.kt b/activity/activity-compose/src/main/java/androidx/activity/compose/PredictiveBackHandler.kt
index 1000765..3bdbb96 100644
--- a/activity/activity-compose/src/main/java/androidx/activity/compose/PredictiveBackHandler.kt
+++ b/activity/activity-compose/src/main/java/androidx/activity/compose/PredictiveBackHandler.kt
@@ -76,10 +76,10 @@
// ensure we don't re-register callbacks when onBack changes
val currentOnBack by rememberUpdatedState(onBack)
val onBackScope = rememberCoroutineScope()
+ var onBackInstance: OnBackInstance? = null
val backCallBack = remember {
object : OnBackPressedCallback(enabled) {
- var onBackInstance: OnBackInstance? = null
override fun handleOnBackStarted(backEvent: BackEventCompat) {
super.handleOnBackStarted(backEvent)
@@ -125,7 +125,13 @@
}
}
- LaunchedEffect(enabled) { backCallBack.isEnabled = enabled }
+ LaunchedEffect(enabled) {
+ backCallBack.isEnabled = enabled
+ if (!enabled) {
+ onBackInstance?.close()
+ onBackInstance = null
+ }
+ }
val backDispatcher =
checkNotNull(LocalOnBackPressedDispatcherOwner.current) {
diff --git a/activity/activity-ktx/api/res-1.10.0-beta01.txt b/activity/activity-ktx/api/res-1.10.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/activity/activity-ktx/api/res-1.10.0-beta01.txt
+++ /dev/null
diff --git a/activity/activity-ktx/api/restricted_1.10.0-beta01.txt b/activity/activity-ktx/api/restricted_1.10.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/activity/activity-ktx/api/restricted_1.10.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/activity/activity/api/1.10.0-beta01.txt b/activity/activity/api/1.10.0-beta01.txt
deleted file mode 100644
index be7377b..0000000
--- a/activity/activity/api/1.10.0-beta01.txt
+++ /dev/null
@@ -1,548 +0,0 @@
-// Signature format: 4.0
-package androidx.activity {
-
- public final class ActivityViewModelLazyKt {
- method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.activity.ComponentActivity, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>? extrasProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
- method @Deprecated @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.activity.ComponentActivity, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
- }
-
- public final class BackEventCompat {
- ctor @RequiresApi(34) public BackEventCompat(android.window.BackEvent backEvent);
- ctor @VisibleForTesting public BackEventCompat(float touchX, float touchY, @FloatRange(from=0.0, to=1.0) float progress, int swipeEdge);
- method public float getProgress();
- method public int getSwipeEdge();
- method public float getTouchX();
- method public float getTouchY();
- method @RequiresApi(34) public android.window.BackEvent toBackEvent();
- property public final float progress;
- property public final int swipeEdge;
- property public final float touchX;
- property public final float touchY;
- field public static final androidx.activity.BackEventCompat.Companion Companion;
- field public static final int EDGE_LEFT = 0; // 0x0
- field public static final int EDGE_RIGHT = 1; // 0x1
- }
-
- public static final class BackEventCompat.Companion {
- }
-
- public class ComponentActivity extends android.app.Activity implements androidx.activity.result.ActivityResultCaller androidx.activity.result.ActivityResultRegistryOwner androidx.activity.contextaware.ContextAware androidx.activity.FullyDrawnReporterOwner androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.core.view.MenuHost androidx.activity.OnBackPressedDispatcherOwner androidx.core.content.OnConfigurationChangedProvider androidx.core.app.OnMultiWindowModeChangedProvider androidx.core.app.OnNewIntentProvider androidx.core.app.OnPictureInPictureModeChangedProvider androidx.core.content.OnTrimMemoryProvider androidx.core.app.OnUserLeaveHintProvider androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
- ctor public ComponentActivity();
- ctor @ContentView public ComponentActivity(@LayoutRes int contentLayoutId);
- method public void addMenuProvider(androidx.core.view.MenuProvider provider);
- method public void addMenuProvider(androidx.core.view.MenuProvider provider, androidx.lifecycle.LifecycleOwner owner);
- method public void addMenuProvider(androidx.core.view.MenuProvider provider, androidx.lifecycle.LifecycleOwner owner, androidx.lifecycle.Lifecycle.State state);
- method public final void addOnConfigurationChangedListener(androidx.core.util.Consumer<android.content.res.Configuration> listener);
- method public final void addOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- method public final void addOnMultiWindowModeChangedListener(androidx.core.util.Consumer<androidx.core.app.MultiWindowModeChangedInfo> listener);
- method public final void addOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
- method public final void addOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
- method public final void addOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
- method public final void addOnUserLeaveHintListener(Runnable listener);
- method public final androidx.activity.result.ActivityResultRegistry getActivityResultRegistry();
- method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
- method public androidx.activity.FullyDrawnReporter getFullyDrawnReporter();
- method @Deprecated public Object? getLastCustomNonConfigurationInstance();
- method public androidx.lifecycle.Lifecycle getLifecycle();
- method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
- method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
- method public androidx.lifecycle.ViewModelStore getViewModelStore();
- method @CallSuper public void initializeViewTreeOwners();
- method public void invalidateMenu();
- method @Deprecated @CallSuper protected void onActivityResult(int requestCode, int resultCode, android.content.Intent? data);
- method @Deprecated @CallSuper public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
- method @Deprecated public Object? onRetainCustomNonConfigurationInstance();
- method public final Object? onRetainNonConfigurationInstance();
- method public android.content.Context? peekAvailableContext();
- method public final <I, O> androidx.activity.result.ActivityResultLauncher<I> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultCallback<O> callback);
- method public final <I, O> androidx.activity.result.ActivityResultLauncher<I> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultRegistry registry, androidx.activity.result.ActivityResultCallback<O> callback);
- method public void removeMenuProvider(androidx.core.view.MenuProvider provider);
- method public final void removeOnConfigurationChangedListener(androidx.core.util.Consumer<android.content.res.Configuration> listener);
- method public final void removeOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- method public final void removeOnMultiWindowModeChangedListener(androidx.core.util.Consumer<androidx.core.app.MultiWindowModeChangedInfo> listener);
- method public final void removeOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
- method public final void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
- method public final void removeOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
- method public final void removeOnUserLeaveHintListener(Runnable listener);
- method @Deprecated public void startActivityForResult(android.content.Intent intent, int requestCode);
- method @Deprecated public void startActivityForResult(android.content.Intent intent, int requestCode, android.os.Bundle? options);
- method @Deprecated @kotlin.jvm.Throws(exceptionClasses=SendIntentException::class) public void startIntentSenderForResult(android.content.IntentSender intent, int requestCode, android.content.Intent? fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws android.content.IntentSender.SendIntentException;
- method @Deprecated @kotlin.jvm.Throws(exceptionClasses=SendIntentException::class) public void startIntentSenderForResult(android.content.IntentSender intent, int requestCode, android.content.Intent? fillInIntent, int flagsMask, int flagsValues, int extraFlags, android.os.Bundle? options) throws android.content.IntentSender.SendIntentException;
- property public final androidx.activity.result.ActivityResultRegistry activityResultRegistry;
- property @CallSuper public androidx.lifecycle.viewmodel.CreationExtras defaultViewModelCreationExtras;
- property public androidx.lifecycle.ViewModelProvider.Factory defaultViewModelProviderFactory;
- property public androidx.activity.FullyDrawnReporter fullyDrawnReporter;
- property @Deprecated public Object? lastCustomNonConfigurationInstance;
- property public androidx.lifecycle.Lifecycle lifecycle;
- property public final androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
- property public final androidx.savedstate.SavedStateRegistry savedStateRegistry;
- property public androidx.lifecycle.ViewModelStore viewModelStore;
- }
-
- public class ComponentDialog extends android.app.Dialog implements androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner {
- ctor public ComponentDialog(android.content.Context context);
- ctor public ComponentDialog(android.content.Context context, optional @StyleRes int themeResId);
- method public androidx.lifecycle.Lifecycle getLifecycle();
- method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
- method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
- method @CallSuper public void initializeViewTreeOwners();
- method @CallSuper public void onBackPressed();
- property public androidx.lifecycle.Lifecycle lifecycle;
- property public final androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
- property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
- }
-
- public final class EdgeToEdge {
- method public static void enable(androidx.activity.ComponentActivity);
- method public static void enable(androidx.activity.ComponentActivity, optional androidx.activity.SystemBarStyle statusBarStyle);
- method public static void enable(androidx.activity.ComponentActivity, optional androidx.activity.SystemBarStyle statusBarStyle, optional androidx.activity.SystemBarStyle navigationBarStyle);
- }
-
- public final class FullyDrawnReporter {
- ctor public FullyDrawnReporter(java.util.concurrent.Executor executor, kotlin.jvm.functions.Function0<kotlin.Unit> reportFullyDrawn);
- method public void addOnReportDrawnListener(kotlin.jvm.functions.Function0<kotlin.Unit> callback);
- method public void addReporter();
- method public boolean isFullyDrawnReported();
- method public void removeOnReportDrawnListener(kotlin.jvm.functions.Function0<kotlin.Unit> callback);
- method public void removeReporter();
- property public final boolean isFullyDrawnReported;
- }
-
- public final class FullyDrawnReporterKt {
- method public static suspend inline Object? reportWhenComplete(androidx.activity.FullyDrawnReporter, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> reporter, kotlin.coroutines.Continuation<? super kotlin.Unit>);
- }
-
- public interface FullyDrawnReporterOwner {
- method public androidx.activity.FullyDrawnReporter getFullyDrawnReporter();
- property public abstract androidx.activity.FullyDrawnReporter fullyDrawnReporter;
- }
-
- public abstract class OnBackPressedCallback {
- ctor public OnBackPressedCallback(boolean enabled);
- method @MainThread public void handleOnBackCancelled();
- method @MainThread public abstract void handleOnBackPressed();
- method @MainThread public void handleOnBackProgressed(androidx.activity.BackEventCompat backEvent);
- method @MainThread public void handleOnBackStarted(androidx.activity.BackEventCompat backEvent);
- method @MainThread public final boolean isEnabled();
- method @MainThread public final void remove();
- method @MainThread public final void setEnabled(boolean);
- property @MainThread public final boolean isEnabled;
- }
-
- public final class OnBackPressedDispatcher {
- ctor public OnBackPressedDispatcher();
- ctor public OnBackPressedDispatcher(optional Runnable? fallbackOnBackPressed);
- ctor public OnBackPressedDispatcher(Runnable? fallbackOnBackPressed, androidx.core.util.Consumer<java.lang.Boolean>? onHasEnabledCallbacksChanged);
- method @MainThread public void addCallback(androidx.activity.OnBackPressedCallback onBackPressedCallback);
- method @MainThread public void addCallback(androidx.lifecycle.LifecycleOwner owner, androidx.activity.OnBackPressedCallback onBackPressedCallback);
- method @MainThread @VisibleForTesting public void dispatchOnBackCancelled();
- method @MainThread @VisibleForTesting public void dispatchOnBackProgressed(androidx.activity.BackEventCompat backEvent);
- method @MainThread @VisibleForTesting public void dispatchOnBackStarted(androidx.activity.BackEventCompat backEvent);
- method @MainThread public boolean hasEnabledCallbacks();
- method @MainThread public void onBackPressed();
- method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public void setOnBackInvokedDispatcher(android.window.OnBackInvokedDispatcher invoker);
- }
-
- public final class OnBackPressedDispatcherKt {
- method public static androidx.activity.OnBackPressedCallback addCallback(androidx.activity.OnBackPressedDispatcher, optional androidx.lifecycle.LifecycleOwner? owner, optional boolean enabled, kotlin.jvm.functions.Function1<? super androidx.activity.OnBackPressedCallback,kotlin.Unit> onBackPressed);
- }
-
- public interface OnBackPressedDispatcherOwner extends androidx.lifecycle.LifecycleOwner {
- method public androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
- property public abstract androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
- }
-
- public final class PipHintTrackerKt {
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static suspend Object? trackPipAnimationHintView(android.app.Activity, android.view.View view, kotlin.coroutines.Continuation<? super kotlin.Unit>);
- }
-
- public final class SystemBarStyle {
- method public static androidx.activity.SystemBarStyle auto(@ColorInt int lightScrim, @ColorInt int darkScrim);
- method public static androidx.activity.SystemBarStyle auto(@ColorInt int lightScrim, @ColorInt int darkScrim, optional kotlin.jvm.functions.Function1<? super android.content.res.Resources,java.lang.Boolean> detectDarkMode);
- method public static androidx.activity.SystemBarStyle dark(@ColorInt int scrim);
- method public static androidx.activity.SystemBarStyle light(@ColorInt int scrim, @ColorInt int darkScrim);
- field public static final androidx.activity.SystemBarStyle.Companion Companion;
- }
-
- public static final class SystemBarStyle.Companion {
- method public androidx.activity.SystemBarStyle auto(@ColorInt int lightScrim, @ColorInt int darkScrim);
- method public androidx.activity.SystemBarStyle auto(@ColorInt int lightScrim, @ColorInt int darkScrim, optional kotlin.jvm.functions.Function1<? super android.content.res.Resources,java.lang.Boolean> detectDarkMode);
- method public androidx.activity.SystemBarStyle dark(@ColorInt int scrim);
- method public androidx.activity.SystemBarStyle light(@ColorInt int scrim, @ColorInt int darkScrim);
- }
-
- public final class ViewTreeFullyDrawnReporterOwner {
- method public static androidx.activity.FullyDrawnReporterOwner? get(android.view.View);
- method public static void set(android.view.View, androidx.activity.FullyDrawnReporterOwner fullyDrawnReporterOwner);
- }
-
- public final class ViewTreeOnBackPressedDispatcherOwner {
- method public static androidx.activity.OnBackPressedDispatcherOwner? get(android.view.View);
- method public static void set(android.view.View, androidx.activity.OnBackPressedDispatcherOwner onBackPressedDispatcherOwner);
- }
-
-}
-
-package androidx.activity.contextaware {
-
- public interface ContextAware {
- method public void addOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- method public android.content.Context? peekAvailableContext();
- method public void removeOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- }
-
- public final class ContextAwareHelper {
- ctor public ContextAwareHelper();
- method public void addOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- method public void clearAvailableContext();
- method public void dispatchOnContextAvailable(android.content.Context context);
- method public android.content.Context? peekAvailableContext();
- method public void removeOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- }
-
- public final class ContextAwareKt {
- method public static suspend inline <R> Object? withContextAvailable(androidx.activity.contextaware.ContextAware, kotlin.jvm.functions.Function1<android.content.Context,R> onContextAvailable, kotlin.coroutines.Continuation<R>);
- }
-
- public fun interface OnContextAvailableListener {
- method public void onContextAvailable(android.content.Context context);
- }
-
-}
-
-package androidx.activity.result {
-
- public final class ActivityResult implements android.os.Parcelable {
- ctor public ActivityResult(int resultCode, android.content.Intent? data);
- method public int describeContents();
- method public android.content.Intent? getData();
- method public int getResultCode();
- method public static String resultCodeToString(int resultCode);
- method public void writeToParcel(android.os.Parcel dest, int flags);
- property public final android.content.Intent? data;
- property public final int resultCode;
- field public static final android.os.Parcelable.Creator<androidx.activity.result.ActivityResult> CREATOR;
- field public static final androidx.activity.result.ActivityResult.Companion Companion;
- }
-
- public static final class ActivityResult.Companion {
- method public String resultCodeToString(int resultCode);
- }
-
- public fun interface ActivityResultCallback<O> {
- method public void onActivityResult(O result);
- }
-
- public interface ActivityResultCaller {
- method public <I, O> androidx.activity.result.ActivityResultLauncher<I> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultCallback<O> callback);
- method public <I, O> androidx.activity.result.ActivityResultLauncher<I> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultRegistry registry, androidx.activity.result.ActivityResultCallback<O> callback);
- }
-
- public final class ActivityResultCallerKt {
- method public static <I, O> androidx.activity.result.ActivityResultLauncher<kotlin.Unit> registerForActivityResult(androidx.activity.result.ActivityResultCaller, androidx.activity.result.contract.ActivityResultContract<I,O> contract, I input, androidx.activity.result.ActivityResultRegistry registry, kotlin.jvm.functions.Function1<O,kotlin.Unit> callback);
- method public static <I, O> androidx.activity.result.ActivityResultLauncher<kotlin.Unit> registerForActivityResult(androidx.activity.result.ActivityResultCaller, androidx.activity.result.contract.ActivityResultContract<I,O> contract, I input, kotlin.jvm.functions.Function1<O,kotlin.Unit> callback);
- }
-
- public final class ActivityResultKt {
- method public static operator int component1(androidx.activity.result.ActivityResult);
- method public static operator android.content.Intent? component2(androidx.activity.result.ActivityResult);
- }
-
- public abstract class ActivityResultLauncher<I> {
- ctor public ActivityResultLauncher();
- method public abstract androidx.activity.result.contract.ActivityResultContract<I,? extends java.lang.Object?> getContract();
- method public void launch(I input);
- method public abstract void launch(I input, androidx.core.app.ActivityOptionsCompat? options);
- method @MainThread public abstract void unregister();
- property public abstract androidx.activity.result.contract.ActivityResultContract<I,? extends java.lang.Object?> contract;
- }
-
- public final class ActivityResultLauncherKt {
- method public static void launch(androidx.activity.result.ActivityResultLauncher<java.lang.Void?>, optional androidx.core.app.ActivityOptionsCompat? options);
- method public static void launchUnit(androidx.activity.result.ActivityResultLauncher<kotlin.Unit>, optional androidx.core.app.ActivityOptionsCompat? options);
- }
-
- public abstract class ActivityResultRegistry {
- ctor public ActivityResultRegistry();
- method @MainThread public final boolean dispatchResult(int requestCode, int resultCode, android.content.Intent? data);
- method @MainThread public final <O> boolean dispatchResult(int requestCode, O result);
- method @MainThread public abstract <I, O> void onLaunch(int requestCode, androidx.activity.result.contract.ActivityResultContract<I,O> contract, I input, androidx.core.app.ActivityOptionsCompat? options);
- method public final void onRestoreInstanceState(android.os.Bundle? savedInstanceState);
- method public final void onSaveInstanceState(android.os.Bundle outState);
- method public final <I, O> androidx.activity.result.ActivityResultLauncher<I> register(String key, androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultCallback<O> callback);
- method public final <I, O> androidx.activity.result.ActivityResultLauncher<I> register(String key, androidx.lifecycle.LifecycleOwner lifecycleOwner, androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultCallback<O> callback);
- }
-
- public interface ActivityResultRegistryOwner {
- method public androidx.activity.result.ActivityResultRegistry getActivityResultRegistry();
- property public abstract androidx.activity.result.ActivityResultRegistry activityResultRegistry;
- }
-
- public final class IntentSenderRequest implements android.os.Parcelable {
- method public int describeContents();
- method public android.content.Intent? getFillInIntent();
- method public int getFlagsMask();
- method public int getFlagsValues();
- method public android.content.IntentSender getIntentSender();
- method public void writeToParcel(android.os.Parcel dest, int flags);
- property public final android.content.Intent? fillInIntent;
- property public final int flagsMask;
- property public final int flagsValues;
- property public final android.content.IntentSender intentSender;
- field public static final android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest> CREATOR;
- field public static final androidx.activity.result.IntentSenderRequest.Companion Companion;
- }
-
- public static final class IntentSenderRequest.Builder {
- ctor public IntentSenderRequest.Builder(android.app.PendingIntent pendingIntent);
- ctor public IntentSenderRequest.Builder(android.content.IntentSender intentSender);
- method public androidx.activity.result.IntentSenderRequest build();
- method public androidx.activity.result.IntentSenderRequest.Builder setFillInIntent(android.content.Intent? fillInIntent);
- method public androidx.activity.result.IntentSenderRequest.Builder setFlags(int values, int mask);
- }
-
- public static final class IntentSenderRequest.Companion {
- }
-
- public final class PickVisualMediaRequest {
- method public long getAccentColor();
- method public androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab getDefaultTab();
- method public int getMaxItems();
- method public androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType getMediaType();
- method public boolean isCustomAccentColorApplied();
- method public boolean isOrderedSelection();
- property public final long accentColor;
- property public final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab defaultTab;
- property public final boolean isCustomAccentColorApplied;
- property public final boolean isOrderedSelection;
- property public final int maxItems;
- property public final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType;
- }
-
- public static final class PickVisualMediaRequest.Builder {
- ctor public PickVisualMediaRequest.Builder();
- method public androidx.activity.result.PickVisualMediaRequest build();
- method public androidx.activity.result.PickVisualMediaRequest.Builder setAccentColor(long accentColor);
- method public androidx.activity.result.PickVisualMediaRequest.Builder setDefaultTab(androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab defaultTab);
- method public androidx.activity.result.PickVisualMediaRequest.Builder setMaxItems(@IntRange(from=2L) int maxItems);
- method public androidx.activity.result.PickVisualMediaRequest.Builder setMediaType(androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType);
- method public androidx.activity.result.PickVisualMediaRequest.Builder setOrderedSelection(boolean isOrderedSelection);
- }
-
- public final class PickVisualMediaRequestKt {
- method @Deprecated public static androidx.activity.result.PickVisualMediaRequest PickVisualMediaRequest(optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType);
- method @Deprecated public static androidx.activity.result.PickVisualMediaRequest PickVisualMediaRequest(optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType, optional @IntRange(from=2L) int maxItems);
- method public static androidx.activity.result.PickVisualMediaRequest PickVisualMediaRequest(optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType, optional @IntRange(from=2L) int maxItems, optional boolean isOrderedSelection, optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab defaultTab);
- method public static androidx.activity.result.PickVisualMediaRequest PickVisualMediaRequest(long accentColor, optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType, optional @IntRange(from=2L) int maxItems, optional boolean isOrderedSelection, optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab defaultTab);
- }
-
-}
-
-package androidx.activity.result.contract {
-
- public abstract class ActivityResultContract<I, O> {
- ctor public ActivityResultContract();
- method public abstract android.content.Intent createIntent(android.content.Context context, I input);
- method public androidx.activity.result.contract.ActivityResultContract.SynchronousResult<O>? getSynchronousResult(android.content.Context context, I input);
- method public abstract O parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static final class ActivityResultContract.SynchronousResult<T> {
- ctor public ActivityResultContract.SynchronousResult(T value);
- method public T getValue();
- property public final T value;
- }
-
- public final class ActivityResultContracts {
- }
-
- public static class ActivityResultContracts.CaptureVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,java.lang.Boolean> {
- ctor public ActivityResultContracts.CaptureVideo();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.lang.Boolean>? getSynchronousResult(android.content.Context context, android.net.Uri input);
- method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
- ctor @Deprecated public ActivityResultContracts.CreateDocument();
- ctor public ActivityResultContracts.CreateDocument(String mimeType);
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String input);
- method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.GetContent extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
- ctor public ActivityResultContracts.GetContent();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String input);
- method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
- ctor public ActivityResultContracts.GetMultipleContents();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri>>? getSynchronousResult(android.content.Context context, String input);
- method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri?> {
- ctor public ActivityResultContracts.OpenDocument();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, String[] input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String[] input);
- method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri?,android.net.Uri?> {
- ctor public ActivityResultContracts.OpenDocumentTree();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri? input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, android.net.Uri? input);
- method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
- ctor public ActivityResultContracts.OpenMultipleDocuments();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, String[] input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri>>? getSynchronousResult(android.content.Context context, String[] input);
- method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static final class ActivityResultContracts.PickContact extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.net.Uri?> {
- ctor public ActivityResultContracts.PickContact();
- method public android.content.Intent createIntent(android.content.Context context, Void? input);
- method public android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.PickMultipleVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,java.util.List<android.net.Uri>> {
- ctor public ActivityResultContracts.PickMultipleVisualMedia();
- ctor public ActivityResultContracts.PickMultipleVisualMedia(optional int maxItems);
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri>>? getSynchronousResult(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
- method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.PickVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,android.net.Uri?> {
- ctor public ActivityResultContracts.PickVisualMedia();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
- method @Deprecated public static final boolean isPhotoPickerAvailable();
- method public static final boolean isPhotoPickerAvailable(android.content.Context context);
- method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- field public static final String ACTION_SYSTEM_FALLBACK_PICK_IMAGES = "androidx.activity.result.contract.action.PICK_IMAGES";
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.Companion Companion;
- field public static final String EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_ACCENT_COLOR = "androidx.activity.result.contract.extra.PICK_IMAGES_ACCENT_COLOR";
- field public static final String EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_IN_ORDER = "androidx.activity.result.contract.extra.PICK_IMAGES_IN_ORDER";
- field public static final String EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_LAUNCH_TAB = "androidx.activity.result.contract.extra.PICK_IMAGES_LAUNCH_TAB";
- field public static final String EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_MAX = "androidx.activity.result.contract.extra.PICK_IMAGES_MAX";
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.Companion {
- method @Deprecated public boolean isPhotoPickerAvailable();
- method public boolean isPhotoPickerAvailable(android.content.Context context);
- }
-
- public abstract static class ActivityResultContracts.PickVisualMedia.DefaultTab {
- method public abstract int getValue();
- property public abstract int value;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.DefaultTab.AlbumsTab extends androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab {
- method public int getValue();
- property public int value;
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab.AlbumsTab INSTANCE;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.DefaultTab.PhotosTab extends androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab {
- method public int getValue();
- property public int value;
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab.PhotosTab INSTANCE;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.ImageAndVideo implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.ImageAndVideo INSTANCE;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.ImageOnly implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.ImageOnly INSTANCE;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.SingleMimeType implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
- ctor public ActivityResultContracts.PickVisualMedia.SingleMimeType(String mimeType);
- method public String getMimeType();
- property public final String mimeType;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.VideoOnly implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VideoOnly INSTANCE;
- }
-
- public static sealed interface ActivityResultContracts.PickVisualMedia.VisualMediaType {
- }
-
- public static final class ActivityResultContracts.RequestMultiplePermissions extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.Map<java.lang.String,java.lang.Boolean>> {
- ctor public ActivityResultContracts.RequestMultiplePermissions();
- method public android.content.Intent createIntent(android.content.Context context, String[] input);
- method public androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.Map<java.lang.String,java.lang.Boolean>>? getSynchronousResult(android.content.Context context, String[] input);
- method public java.util.Map<java.lang.String,java.lang.Boolean> parseResult(int resultCode, android.content.Intent? intent);
- field public static final String ACTION_REQUEST_PERMISSIONS = "androidx.activity.result.contract.action.REQUEST_PERMISSIONS";
- field public static final androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions.Companion Companion;
- field public static final String EXTRA_PERMISSIONS = "androidx.activity.result.contract.extra.PERMISSIONS";
- field public static final String EXTRA_PERMISSION_GRANT_RESULTS = "androidx.activity.result.contract.extra.PERMISSION_GRANT_RESULTS";
- }
-
- public static final class ActivityResultContracts.RequestMultiplePermissions.Companion {
- }
-
- public static final class ActivityResultContracts.RequestPermission extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.lang.Boolean> {
- ctor public ActivityResultContracts.RequestPermission();
- method public android.content.Intent createIntent(android.content.Context context, String input);
- method public androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.lang.Boolean>? getSynchronousResult(android.content.Context context, String input);
- method public Boolean parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static final class ActivityResultContracts.StartActivityForResult extends androidx.activity.result.contract.ActivityResultContract<android.content.Intent,androidx.activity.result.ActivityResult> {
- ctor public ActivityResultContracts.StartActivityForResult();
- method public android.content.Intent createIntent(android.content.Context context, android.content.Intent input);
- method public androidx.activity.result.ActivityResult parseResult(int resultCode, android.content.Intent? intent);
- field public static final androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult.Companion Companion;
- field public static final String EXTRA_ACTIVITY_OPTIONS_BUNDLE = "androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE";
- }
-
- public static final class ActivityResultContracts.StartActivityForResult.Companion {
- }
-
- public static final class ActivityResultContracts.StartIntentSenderForResult extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.IntentSenderRequest,androidx.activity.result.ActivityResult> {
- ctor public ActivityResultContracts.StartIntentSenderForResult();
- method public android.content.Intent createIntent(android.content.Context context, androidx.activity.result.IntentSenderRequest input);
- method public androidx.activity.result.ActivityResult parseResult(int resultCode, android.content.Intent? intent);
- field public static final String ACTION_INTENT_SENDER_REQUEST = "androidx.activity.result.contract.action.INTENT_SENDER_REQUEST";
- field public static final androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult.Companion Companion;
- field public static final String EXTRA_INTENT_SENDER_REQUEST = "androidx.activity.result.contract.extra.INTENT_SENDER_REQUEST";
- field public static final String EXTRA_SEND_INTENT_EXCEPTION = "androidx.activity.result.contract.extra.SEND_INTENT_EXCEPTION";
- }
-
- public static final class ActivityResultContracts.StartIntentSenderForResult.Companion {
- }
-
- public static class ActivityResultContracts.TakePicture extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,java.lang.Boolean> {
- ctor public ActivityResultContracts.TakePicture();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.lang.Boolean>? getSynchronousResult(android.content.Context context, android.net.Uri input);
- method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.TakePicturePreview extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.graphics.Bitmap?> {
- ctor public ActivityResultContracts.TakePicturePreview();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, Void? input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, Void? input);
- method public final android.graphics.Bitmap? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- @Deprecated public static class ActivityResultContracts.TakeVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.graphics.Bitmap?> {
- ctor @Deprecated public ActivityResultContracts.TakeVideo();
- method @Deprecated @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
- method @Deprecated public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, android.net.Uri input);
- method @Deprecated public final android.graphics.Bitmap? parseResult(int resultCode, android.content.Intent? intent);
- }
-
-}
-
diff --git a/activity/activity/api/restricted_1.10.0-beta01.txt b/activity/activity/api/restricted_1.10.0-beta01.txt
deleted file mode 100644
index 3fc0729..0000000
--- a/activity/activity/api/restricted_1.10.0-beta01.txt
+++ /dev/null
@@ -1,547 +0,0 @@
-// Signature format: 4.0
-package androidx.activity {
-
- public final class ActivityViewModelLazyKt {
- method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.activity.ComponentActivity, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>? extrasProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
- method @Deprecated @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.activity.ComponentActivity, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
- }
-
- public final class BackEventCompat {
- ctor @RequiresApi(34) public BackEventCompat(android.window.BackEvent backEvent);
- ctor @VisibleForTesting public BackEventCompat(float touchX, float touchY, @FloatRange(from=0.0, to=1.0) float progress, int swipeEdge);
- method public float getProgress();
- method public int getSwipeEdge();
- method public float getTouchX();
- method public float getTouchY();
- method @RequiresApi(34) public android.window.BackEvent toBackEvent();
- property public final float progress;
- property public final int swipeEdge;
- property public final float touchX;
- property public final float touchY;
- field public static final androidx.activity.BackEventCompat.Companion Companion;
- field public static final int EDGE_LEFT = 0; // 0x0
- field public static final int EDGE_RIGHT = 1; // 0x1
- }
-
- public static final class BackEventCompat.Companion {
- }
-
- public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.activity.result.ActivityResultCaller androidx.activity.result.ActivityResultRegistryOwner androidx.activity.contextaware.ContextAware androidx.activity.FullyDrawnReporterOwner androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.core.view.MenuHost androidx.activity.OnBackPressedDispatcherOwner androidx.core.content.OnConfigurationChangedProvider androidx.core.app.OnMultiWindowModeChangedProvider androidx.core.app.OnNewIntentProvider androidx.core.app.OnPictureInPictureModeChangedProvider androidx.core.content.OnTrimMemoryProvider androidx.core.app.OnUserLeaveHintProvider androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
- ctor public ComponentActivity();
- ctor @ContentView public ComponentActivity(@LayoutRes int contentLayoutId);
- method public void addMenuProvider(androidx.core.view.MenuProvider provider);
- method public void addMenuProvider(androidx.core.view.MenuProvider provider, androidx.lifecycle.LifecycleOwner owner);
- method public void addMenuProvider(androidx.core.view.MenuProvider provider, androidx.lifecycle.LifecycleOwner owner, androidx.lifecycle.Lifecycle.State state);
- method public final void addOnConfigurationChangedListener(androidx.core.util.Consumer<android.content.res.Configuration> listener);
- method public final void addOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- method public final void addOnMultiWindowModeChangedListener(androidx.core.util.Consumer<androidx.core.app.MultiWindowModeChangedInfo> listener);
- method public final void addOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
- method public final void addOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
- method public final void addOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
- method public final void addOnUserLeaveHintListener(Runnable listener);
- method public final androidx.activity.result.ActivityResultRegistry getActivityResultRegistry();
- method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
- method public androidx.activity.FullyDrawnReporter getFullyDrawnReporter();
- method @Deprecated public Object? getLastCustomNonConfigurationInstance();
- method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
- method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
- method public androidx.lifecycle.ViewModelStore getViewModelStore();
- method @CallSuper public void initializeViewTreeOwners();
- method public void invalidateMenu();
- method @Deprecated @CallSuper protected void onActivityResult(int requestCode, int resultCode, android.content.Intent? data);
- method @Deprecated @CallSuper public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
- method @Deprecated public Object? onRetainCustomNonConfigurationInstance();
- method public final Object? onRetainNonConfigurationInstance();
- method public android.content.Context? peekAvailableContext();
- method public final <I, O> androidx.activity.result.ActivityResultLauncher<I> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultCallback<O> callback);
- method public final <I, O> androidx.activity.result.ActivityResultLauncher<I> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultRegistry registry, androidx.activity.result.ActivityResultCallback<O> callback);
- method public void removeMenuProvider(androidx.core.view.MenuProvider provider);
- method public final void removeOnConfigurationChangedListener(androidx.core.util.Consumer<android.content.res.Configuration> listener);
- method public final void removeOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- method public final void removeOnMultiWindowModeChangedListener(androidx.core.util.Consumer<androidx.core.app.MultiWindowModeChangedInfo> listener);
- method public final void removeOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
- method public final void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
- method public final void removeOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
- method public final void removeOnUserLeaveHintListener(Runnable listener);
- method @Deprecated public void startActivityForResult(android.content.Intent intent, int requestCode);
- method @Deprecated public void startActivityForResult(android.content.Intent intent, int requestCode, android.os.Bundle? options);
- method @Deprecated @kotlin.jvm.Throws(exceptionClasses=SendIntentException::class) public void startIntentSenderForResult(android.content.IntentSender intent, int requestCode, android.content.Intent? fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws android.content.IntentSender.SendIntentException;
- method @Deprecated @kotlin.jvm.Throws(exceptionClasses=SendIntentException::class) public void startIntentSenderForResult(android.content.IntentSender intent, int requestCode, android.content.Intent? fillInIntent, int flagsMask, int flagsValues, int extraFlags, android.os.Bundle? options) throws android.content.IntentSender.SendIntentException;
- property public final androidx.activity.result.ActivityResultRegistry activityResultRegistry;
- property @CallSuper public androidx.lifecycle.viewmodel.CreationExtras defaultViewModelCreationExtras;
- property public androidx.lifecycle.ViewModelProvider.Factory defaultViewModelProviderFactory;
- property public androidx.activity.FullyDrawnReporter fullyDrawnReporter;
- property @Deprecated public Object? lastCustomNonConfigurationInstance;
- property public androidx.lifecycle.Lifecycle lifecycle;
- property public final androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
- property public final androidx.savedstate.SavedStateRegistry savedStateRegistry;
- property public androidx.lifecycle.ViewModelStore viewModelStore;
- }
-
- public class ComponentDialog extends android.app.Dialog implements androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner {
- ctor public ComponentDialog(android.content.Context context);
- ctor public ComponentDialog(android.content.Context context, optional @StyleRes int themeResId);
- method public androidx.lifecycle.Lifecycle getLifecycle();
- method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
- method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
- method @CallSuper public void initializeViewTreeOwners();
- method @CallSuper public void onBackPressed();
- property public androidx.lifecycle.Lifecycle lifecycle;
- property public final androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
- property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
- }
-
- public final class EdgeToEdge {
- method public static void enable(androidx.activity.ComponentActivity);
- method public static void enable(androidx.activity.ComponentActivity, optional androidx.activity.SystemBarStyle statusBarStyle);
- method public static void enable(androidx.activity.ComponentActivity, optional androidx.activity.SystemBarStyle statusBarStyle, optional androidx.activity.SystemBarStyle navigationBarStyle);
- }
-
- public final class FullyDrawnReporter {
- ctor public FullyDrawnReporter(java.util.concurrent.Executor executor, kotlin.jvm.functions.Function0<kotlin.Unit> reportFullyDrawn);
- method public void addOnReportDrawnListener(kotlin.jvm.functions.Function0<kotlin.Unit> callback);
- method public void addReporter();
- method public boolean isFullyDrawnReported();
- method public void removeOnReportDrawnListener(kotlin.jvm.functions.Function0<kotlin.Unit> callback);
- method public void removeReporter();
- property public final boolean isFullyDrawnReported;
- }
-
- public final class FullyDrawnReporterKt {
- method public static suspend inline Object? reportWhenComplete(androidx.activity.FullyDrawnReporter, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> reporter, kotlin.coroutines.Continuation<? super kotlin.Unit>);
- }
-
- public interface FullyDrawnReporterOwner {
- method public androidx.activity.FullyDrawnReporter getFullyDrawnReporter();
- property public abstract androidx.activity.FullyDrawnReporter fullyDrawnReporter;
- }
-
- public abstract class OnBackPressedCallback {
- ctor public OnBackPressedCallback(boolean enabled);
- method @MainThread public void handleOnBackCancelled();
- method @MainThread public abstract void handleOnBackPressed();
- method @MainThread public void handleOnBackProgressed(androidx.activity.BackEventCompat backEvent);
- method @MainThread public void handleOnBackStarted(androidx.activity.BackEventCompat backEvent);
- method @MainThread public final boolean isEnabled();
- method @MainThread public final void remove();
- method @MainThread public final void setEnabled(boolean);
- property @MainThread public final boolean isEnabled;
- }
-
- public final class OnBackPressedDispatcher {
- ctor public OnBackPressedDispatcher();
- ctor public OnBackPressedDispatcher(optional Runnable? fallbackOnBackPressed);
- ctor public OnBackPressedDispatcher(Runnable? fallbackOnBackPressed, androidx.core.util.Consumer<java.lang.Boolean>? onHasEnabledCallbacksChanged);
- method @MainThread public void addCallback(androidx.activity.OnBackPressedCallback onBackPressedCallback);
- method @MainThread public void addCallback(androidx.lifecycle.LifecycleOwner owner, androidx.activity.OnBackPressedCallback onBackPressedCallback);
- method @MainThread @VisibleForTesting public void dispatchOnBackCancelled();
- method @MainThread @VisibleForTesting public void dispatchOnBackProgressed(androidx.activity.BackEventCompat backEvent);
- method @MainThread @VisibleForTesting public void dispatchOnBackStarted(androidx.activity.BackEventCompat backEvent);
- method @MainThread public boolean hasEnabledCallbacks();
- method @MainThread public void onBackPressed();
- method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public void setOnBackInvokedDispatcher(android.window.OnBackInvokedDispatcher invoker);
- }
-
- public final class OnBackPressedDispatcherKt {
- method public static androidx.activity.OnBackPressedCallback addCallback(androidx.activity.OnBackPressedDispatcher, optional androidx.lifecycle.LifecycleOwner? owner, optional boolean enabled, kotlin.jvm.functions.Function1<? super androidx.activity.OnBackPressedCallback,kotlin.Unit> onBackPressed);
- }
-
- public interface OnBackPressedDispatcherOwner extends androidx.lifecycle.LifecycleOwner {
- method public androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
- property public abstract androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
- }
-
- public final class PipHintTrackerKt {
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static suspend Object? trackPipAnimationHintView(android.app.Activity, android.view.View view, kotlin.coroutines.Continuation<? super kotlin.Unit>);
- }
-
- public final class SystemBarStyle {
- method public static androidx.activity.SystemBarStyle auto(@ColorInt int lightScrim, @ColorInt int darkScrim);
- method public static androidx.activity.SystemBarStyle auto(@ColorInt int lightScrim, @ColorInt int darkScrim, optional kotlin.jvm.functions.Function1<? super android.content.res.Resources,java.lang.Boolean> detectDarkMode);
- method public static androidx.activity.SystemBarStyle dark(@ColorInt int scrim);
- method public static androidx.activity.SystemBarStyle light(@ColorInt int scrim, @ColorInt int darkScrim);
- field public static final androidx.activity.SystemBarStyle.Companion Companion;
- }
-
- public static final class SystemBarStyle.Companion {
- method public androidx.activity.SystemBarStyle auto(@ColorInt int lightScrim, @ColorInt int darkScrim);
- method public androidx.activity.SystemBarStyle auto(@ColorInt int lightScrim, @ColorInt int darkScrim, optional kotlin.jvm.functions.Function1<? super android.content.res.Resources,java.lang.Boolean> detectDarkMode);
- method public androidx.activity.SystemBarStyle dark(@ColorInt int scrim);
- method public androidx.activity.SystemBarStyle light(@ColorInt int scrim, @ColorInt int darkScrim);
- }
-
- public final class ViewTreeFullyDrawnReporterOwner {
- method public static androidx.activity.FullyDrawnReporterOwner? get(android.view.View);
- method public static void set(android.view.View, androidx.activity.FullyDrawnReporterOwner fullyDrawnReporterOwner);
- }
-
- public final class ViewTreeOnBackPressedDispatcherOwner {
- method public static androidx.activity.OnBackPressedDispatcherOwner? get(android.view.View);
- method public static void set(android.view.View, androidx.activity.OnBackPressedDispatcherOwner onBackPressedDispatcherOwner);
- }
-
-}
-
-package androidx.activity.contextaware {
-
- public interface ContextAware {
- method public void addOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- method public android.content.Context? peekAvailableContext();
- method public void removeOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- }
-
- public final class ContextAwareHelper {
- ctor public ContextAwareHelper();
- method public void addOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- method public void clearAvailableContext();
- method public void dispatchOnContextAvailable(android.content.Context context);
- method public android.content.Context? peekAvailableContext();
- method public void removeOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener listener);
- }
-
- public final class ContextAwareKt {
- method public static suspend inline <R> Object? withContextAvailable(androidx.activity.contextaware.ContextAware, kotlin.jvm.functions.Function1<android.content.Context,R> onContextAvailable, kotlin.coroutines.Continuation<R>);
- }
-
- public fun interface OnContextAvailableListener {
- method public void onContextAvailable(android.content.Context context);
- }
-
-}
-
-package androidx.activity.result {
-
- public final class ActivityResult implements android.os.Parcelable {
- ctor public ActivityResult(int resultCode, android.content.Intent? data);
- method public int describeContents();
- method public android.content.Intent? getData();
- method public int getResultCode();
- method public static String resultCodeToString(int resultCode);
- method public void writeToParcel(android.os.Parcel dest, int flags);
- property public final android.content.Intent? data;
- property public final int resultCode;
- field public static final android.os.Parcelable.Creator<androidx.activity.result.ActivityResult> CREATOR;
- field public static final androidx.activity.result.ActivityResult.Companion Companion;
- }
-
- public static final class ActivityResult.Companion {
- method public String resultCodeToString(int resultCode);
- }
-
- public fun interface ActivityResultCallback<O> {
- method public void onActivityResult(O result);
- }
-
- public interface ActivityResultCaller {
- method public <I, O> androidx.activity.result.ActivityResultLauncher<I> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultCallback<O> callback);
- method public <I, O> androidx.activity.result.ActivityResultLauncher<I> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultRegistry registry, androidx.activity.result.ActivityResultCallback<O> callback);
- }
-
- public final class ActivityResultCallerKt {
- method public static <I, O> androidx.activity.result.ActivityResultLauncher<kotlin.Unit> registerForActivityResult(androidx.activity.result.ActivityResultCaller, androidx.activity.result.contract.ActivityResultContract<I,O> contract, I input, androidx.activity.result.ActivityResultRegistry registry, kotlin.jvm.functions.Function1<O,kotlin.Unit> callback);
- method public static <I, O> androidx.activity.result.ActivityResultLauncher<kotlin.Unit> registerForActivityResult(androidx.activity.result.ActivityResultCaller, androidx.activity.result.contract.ActivityResultContract<I,O> contract, I input, kotlin.jvm.functions.Function1<O,kotlin.Unit> callback);
- }
-
- public final class ActivityResultKt {
- method public static operator int component1(androidx.activity.result.ActivityResult);
- method public static operator android.content.Intent? component2(androidx.activity.result.ActivityResult);
- }
-
- public abstract class ActivityResultLauncher<I> {
- ctor public ActivityResultLauncher();
- method public abstract androidx.activity.result.contract.ActivityResultContract<I,? extends java.lang.Object?> getContract();
- method public void launch(I input);
- method public abstract void launch(I input, androidx.core.app.ActivityOptionsCompat? options);
- method @MainThread public abstract void unregister();
- property public abstract androidx.activity.result.contract.ActivityResultContract<I,? extends java.lang.Object?> contract;
- }
-
- public final class ActivityResultLauncherKt {
- method public static void launch(androidx.activity.result.ActivityResultLauncher<java.lang.Void?>, optional androidx.core.app.ActivityOptionsCompat? options);
- method public static void launchUnit(androidx.activity.result.ActivityResultLauncher<kotlin.Unit>, optional androidx.core.app.ActivityOptionsCompat? options);
- }
-
- public abstract class ActivityResultRegistry {
- ctor public ActivityResultRegistry();
- method @MainThread public final boolean dispatchResult(int requestCode, int resultCode, android.content.Intent? data);
- method @MainThread public final <O> boolean dispatchResult(int requestCode, O result);
- method @MainThread public abstract <I, O> void onLaunch(int requestCode, androidx.activity.result.contract.ActivityResultContract<I,O> contract, I input, androidx.core.app.ActivityOptionsCompat? options);
- method public final void onRestoreInstanceState(android.os.Bundle? savedInstanceState);
- method public final void onSaveInstanceState(android.os.Bundle outState);
- method public final <I, O> androidx.activity.result.ActivityResultLauncher<I> register(String key, androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultCallback<O> callback);
- method public final <I, O> androidx.activity.result.ActivityResultLauncher<I> register(String key, androidx.lifecycle.LifecycleOwner lifecycleOwner, androidx.activity.result.contract.ActivityResultContract<I,O> contract, androidx.activity.result.ActivityResultCallback<O> callback);
- }
-
- public interface ActivityResultRegistryOwner {
- method public androidx.activity.result.ActivityResultRegistry getActivityResultRegistry();
- property public abstract androidx.activity.result.ActivityResultRegistry activityResultRegistry;
- }
-
- public final class IntentSenderRequest implements android.os.Parcelable {
- method public int describeContents();
- method public android.content.Intent? getFillInIntent();
- method public int getFlagsMask();
- method public int getFlagsValues();
- method public android.content.IntentSender getIntentSender();
- method public void writeToParcel(android.os.Parcel dest, int flags);
- property public final android.content.Intent? fillInIntent;
- property public final int flagsMask;
- property public final int flagsValues;
- property public final android.content.IntentSender intentSender;
- field public static final android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest> CREATOR;
- field public static final androidx.activity.result.IntentSenderRequest.Companion Companion;
- }
-
- public static final class IntentSenderRequest.Builder {
- ctor public IntentSenderRequest.Builder(android.app.PendingIntent pendingIntent);
- ctor public IntentSenderRequest.Builder(android.content.IntentSender intentSender);
- method public androidx.activity.result.IntentSenderRequest build();
- method public androidx.activity.result.IntentSenderRequest.Builder setFillInIntent(android.content.Intent? fillInIntent);
- method public androidx.activity.result.IntentSenderRequest.Builder setFlags(int values, int mask);
- }
-
- public static final class IntentSenderRequest.Companion {
- }
-
- public final class PickVisualMediaRequest {
- method public long getAccentColor();
- method public androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab getDefaultTab();
- method public int getMaxItems();
- method public androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType getMediaType();
- method public boolean isCustomAccentColorApplied();
- method public boolean isOrderedSelection();
- property public final long accentColor;
- property public final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab defaultTab;
- property public final boolean isCustomAccentColorApplied;
- property public final boolean isOrderedSelection;
- property public final int maxItems;
- property public final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType;
- }
-
- public static final class PickVisualMediaRequest.Builder {
- ctor public PickVisualMediaRequest.Builder();
- method public androidx.activity.result.PickVisualMediaRequest build();
- method public androidx.activity.result.PickVisualMediaRequest.Builder setAccentColor(long accentColor);
- method public androidx.activity.result.PickVisualMediaRequest.Builder setDefaultTab(androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab defaultTab);
- method public androidx.activity.result.PickVisualMediaRequest.Builder setMaxItems(@IntRange(from=2L) int maxItems);
- method public androidx.activity.result.PickVisualMediaRequest.Builder setMediaType(androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType);
- method public androidx.activity.result.PickVisualMediaRequest.Builder setOrderedSelection(boolean isOrderedSelection);
- }
-
- public final class PickVisualMediaRequestKt {
- method @Deprecated public static androidx.activity.result.PickVisualMediaRequest PickVisualMediaRequest(optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType);
- method @Deprecated public static androidx.activity.result.PickVisualMediaRequest PickVisualMediaRequest(optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType, optional @IntRange(from=2L) int maxItems);
- method public static androidx.activity.result.PickVisualMediaRequest PickVisualMediaRequest(optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType, optional @IntRange(from=2L) int maxItems, optional boolean isOrderedSelection, optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab defaultTab);
- method public static androidx.activity.result.PickVisualMediaRequest PickVisualMediaRequest(long accentColor, optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType, optional @IntRange(from=2L) int maxItems, optional boolean isOrderedSelection, optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab defaultTab);
- }
-
-}
-
-package androidx.activity.result.contract {
-
- public abstract class ActivityResultContract<I, O> {
- ctor public ActivityResultContract();
- method public abstract android.content.Intent createIntent(android.content.Context context, I input);
- method public androidx.activity.result.contract.ActivityResultContract.SynchronousResult<O>? getSynchronousResult(android.content.Context context, I input);
- method public abstract O parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static final class ActivityResultContract.SynchronousResult<T> {
- ctor public ActivityResultContract.SynchronousResult(T value);
- method public T getValue();
- property public final T value;
- }
-
- public final class ActivityResultContracts {
- }
-
- public static class ActivityResultContracts.CaptureVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,java.lang.Boolean> {
- ctor public ActivityResultContracts.CaptureVideo();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.lang.Boolean>? getSynchronousResult(android.content.Context context, android.net.Uri input);
- method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
- ctor @Deprecated public ActivityResultContracts.CreateDocument();
- ctor public ActivityResultContracts.CreateDocument(String mimeType);
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String input);
- method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.GetContent extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
- ctor public ActivityResultContracts.GetContent();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String input);
- method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
- ctor public ActivityResultContracts.GetMultipleContents();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri>>? getSynchronousResult(android.content.Context context, String input);
- method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri?> {
- ctor public ActivityResultContracts.OpenDocument();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, String[] input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String[] input);
- method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri?,android.net.Uri?> {
- ctor public ActivityResultContracts.OpenDocumentTree();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri? input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, android.net.Uri? input);
- method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
- ctor public ActivityResultContracts.OpenMultipleDocuments();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, String[] input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri>>? getSynchronousResult(android.content.Context context, String[] input);
- method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static final class ActivityResultContracts.PickContact extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.net.Uri?> {
- ctor public ActivityResultContracts.PickContact();
- method public android.content.Intent createIntent(android.content.Context context, Void? input);
- method public android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.PickMultipleVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,java.util.List<android.net.Uri>> {
- ctor public ActivityResultContracts.PickMultipleVisualMedia();
- ctor public ActivityResultContracts.PickMultipleVisualMedia(optional int maxItems);
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri>>? getSynchronousResult(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
- method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.PickVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,android.net.Uri?> {
- ctor public ActivityResultContracts.PickVisualMedia();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
- method @Deprecated public static final boolean isPhotoPickerAvailable();
- method public static final boolean isPhotoPickerAvailable(android.content.Context context);
- method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
- field public static final String ACTION_SYSTEM_FALLBACK_PICK_IMAGES = "androidx.activity.result.contract.action.PICK_IMAGES";
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.Companion Companion;
- field public static final String EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_ACCENT_COLOR = "androidx.activity.result.contract.extra.PICK_IMAGES_ACCENT_COLOR";
- field public static final String EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_IN_ORDER = "androidx.activity.result.contract.extra.PICK_IMAGES_IN_ORDER";
- field public static final String EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_LAUNCH_TAB = "androidx.activity.result.contract.extra.PICK_IMAGES_LAUNCH_TAB";
- field public static final String EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_MAX = "androidx.activity.result.contract.extra.PICK_IMAGES_MAX";
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.Companion {
- method @Deprecated public boolean isPhotoPickerAvailable();
- method public boolean isPhotoPickerAvailable(android.content.Context context);
- }
-
- public abstract static class ActivityResultContracts.PickVisualMedia.DefaultTab {
- method public abstract int getValue();
- property public abstract int value;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.DefaultTab.AlbumsTab extends androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab {
- method public int getValue();
- property public int value;
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab.AlbumsTab INSTANCE;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.DefaultTab.PhotosTab extends androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab {
- method public int getValue();
- property public int value;
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.DefaultTab.PhotosTab INSTANCE;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.ImageAndVideo implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.ImageAndVideo INSTANCE;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.ImageOnly implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.ImageOnly INSTANCE;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.SingleMimeType implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
- ctor public ActivityResultContracts.PickVisualMedia.SingleMimeType(String mimeType);
- method public String getMimeType();
- property public final String mimeType;
- }
-
- public static final class ActivityResultContracts.PickVisualMedia.VideoOnly implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
- field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VideoOnly INSTANCE;
- }
-
- public static sealed interface ActivityResultContracts.PickVisualMedia.VisualMediaType {
- }
-
- public static final class ActivityResultContracts.RequestMultiplePermissions extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.Map<java.lang.String,java.lang.Boolean>> {
- ctor public ActivityResultContracts.RequestMultiplePermissions();
- method public android.content.Intent createIntent(android.content.Context context, String[] input);
- method public androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.Map<java.lang.String,java.lang.Boolean>>? getSynchronousResult(android.content.Context context, String[] input);
- method public java.util.Map<java.lang.String,java.lang.Boolean> parseResult(int resultCode, android.content.Intent? intent);
- field public static final String ACTION_REQUEST_PERMISSIONS = "androidx.activity.result.contract.action.REQUEST_PERMISSIONS";
- field public static final androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions.Companion Companion;
- field public static final String EXTRA_PERMISSIONS = "androidx.activity.result.contract.extra.PERMISSIONS";
- field public static final String EXTRA_PERMISSION_GRANT_RESULTS = "androidx.activity.result.contract.extra.PERMISSION_GRANT_RESULTS";
- }
-
- public static final class ActivityResultContracts.RequestMultiplePermissions.Companion {
- }
-
- public static final class ActivityResultContracts.RequestPermission extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.lang.Boolean> {
- ctor public ActivityResultContracts.RequestPermission();
- method public android.content.Intent createIntent(android.content.Context context, String input);
- method public androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.lang.Boolean>? getSynchronousResult(android.content.Context context, String input);
- method public Boolean parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static final class ActivityResultContracts.StartActivityForResult extends androidx.activity.result.contract.ActivityResultContract<android.content.Intent,androidx.activity.result.ActivityResult> {
- ctor public ActivityResultContracts.StartActivityForResult();
- method public android.content.Intent createIntent(android.content.Context context, android.content.Intent input);
- method public androidx.activity.result.ActivityResult parseResult(int resultCode, android.content.Intent? intent);
- field public static final androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult.Companion Companion;
- field public static final String EXTRA_ACTIVITY_OPTIONS_BUNDLE = "androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE";
- }
-
- public static final class ActivityResultContracts.StartActivityForResult.Companion {
- }
-
- public static final class ActivityResultContracts.StartIntentSenderForResult extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.IntentSenderRequest,androidx.activity.result.ActivityResult> {
- ctor public ActivityResultContracts.StartIntentSenderForResult();
- method public android.content.Intent createIntent(android.content.Context context, androidx.activity.result.IntentSenderRequest input);
- method public androidx.activity.result.ActivityResult parseResult(int resultCode, android.content.Intent? intent);
- field public static final String ACTION_INTENT_SENDER_REQUEST = "androidx.activity.result.contract.action.INTENT_SENDER_REQUEST";
- field public static final androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult.Companion Companion;
- field public static final String EXTRA_INTENT_SENDER_REQUEST = "androidx.activity.result.contract.extra.INTENT_SENDER_REQUEST";
- field public static final String EXTRA_SEND_INTENT_EXCEPTION = "androidx.activity.result.contract.extra.SEND_INTENT_EXCEPTION";
- }
-
- public static final class ActivityResultContracts.StartIntentSenderForResult.Companion {
- }
-
- public static class ActivityResultContracts.TakePicture extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,java.lang.Boolean> {
- ctor public ActivityResultContracts.TakePicture();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.lang.Boolean>? getSynchronousResult(android.content.Context context, android.net.Uri input);
- method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
- }
-
- public static class ActivityResultContracts.TakePicturePreview extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.graphics.Bitmap?> {
- ctor public ActivityResultContracts.TakePicturePreview();
- method @CallSuper public android.content.Intent createIntent(android.content.Context context, Void? input);
- method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, Void? input);
- method public final android.graphics.Bitmap? parseResult(int resultCode, android.content.Intent? intent);
- }
-
- @Deprecated public static class ActivityResultContracts.TakeVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.graphics.Bitmap?> {
- ctor @Deprecated public ActivityResultContracts.TakeVideo();
- method @Deprecated @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
- method @Deprecated public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, android.net.Uri input);
- method @Deprecated public final android.graphics.Bitmap? parseResult(int resultCode, android.content.Intent? intent);
- }
-
-}
-
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/EnterpriseGlobalSearchSessionPlatformCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/EnterpriseGlobalSearchSessionPlatformCtsTest.java
index 968ce31..97f487a 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/EnterpriseGlobalSearchSessionPlatformCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/EnterpriseGlobalSearchSessionPlatformCtsTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+// @exportToFramework:skipFile()
package androidx.appsearch.cts.app;
import android.content.Context;
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GenericDocumentCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GenericDocumentCtsTest.java
index b666e4d..74a03e5 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GenericDocumentCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GenericDocumentCtsTest.java
@@ -20,12 +20,16 @@
import static org.junit.Assert.assertThrows;
+import android.os.Build;
+import android.os.Parcel;
+
import androidx.appsearch.app.EmbeddingVector;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.flags.CheckFlagsRule;
import androidx.appsearch.flags.DeviceFlagsValueProvider;
import androidx.appsearch.flags.Flags;
import androidx.appsearch.flags.RequiresFlagsEnabled;
+import androidx.test.filters.SdkSuppress;
import org.junit.Rule;
import org.junit.Test;
@@ -1219,4 +1223,39 @@
() -> new EmbeddingVector(new float[]{}, "my_model"));
assertThat(exception).hasMessageThat().contains("Embedding values cannot be empty.");
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_GENERIC_DOCUMENT_OVER_IPC)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public void testWriteToParcel() {
+ GenericDocument inDoc =
+ new GenericDocument.Builder<>("namespace", "id1", "schema1")
+ .setScore(42)
+ .setPropertyString("propString", "Hello")
+ .setPropertyBytes("propBytes", new byte[][] {{1, 2}})
+ .setPropertyDocument(
+ "propDocument",
+ new GenericDocument.Builder<>("namespace", "id2", "schema2")
+ .setPropertyString("propString", "Goodbye")
+ .setPropertyBytes("propBytes", new byte[][] {{3, 4}})
+ .build())
+ .build();
+
+ // Serialize the document
+ Parcel parcel = Parcel.obtain();
+ inDoc.writeToParcel(parcel, /* flags= */ 0);
+
+ // Deserialize the document
+ parcel.setDataPosition(0);
+ GenericDocument document = GenericDocument.createFromParcel(parcel);
+ parcel.recycle();
+
+ // Compare results
+ assertThat(document.getPropertyString("propString")).isEqualTo("Hello");
+ assertThat(document.getPropertyBytesArray("propBytes")).isEqualTo(new byte[][] {{1, 2}});
+ assertThat(document.getPropertyDocument("propDocument").getPropertyString("propString"))
+ .isEqualTo("Goodbye");
+ assertThat(document.getPropertyDocument("propDocument").getPropertyBytesArray("propBytes"))
+ .isEqualTo(new byte[][] {{3, 4}});
+ }
}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
index 1428f0f..d4856d7 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
@@ -113,7 +113,7 @@
public void testFlagValue_enableSearchSpecSearchStringParameters() {
assertThat(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
.isEqualTo(
- "com.android.appsearch.flags.enable_search_spec_search_spec_strings");
+ "com.android.appsearch.flags.enable_search_spec_search_string_parameters");
}
@Test
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/CurrentTimeMillisLong.java b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/CurrentTimeMillisLong.java
new file mode 100644
index 0000000..2b1d3f4
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/CurrentTimeMillisLong.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+// @exportToFramework:skipFile()
+package androidx.appsearch.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative timestamp measured as the number of
+ * milliseconds since 1970-01-01T00:00:00Z.
+ * @paramDoc Value is a non-negative timestamp measured as the number of
+ * milliseconds since 1970-01-01T00:00:00Z.
+ * @returnDoc Value is a non-negative timestamp measured as the number of
+ * milliseconds since 1970-01-01T00:00:00Z.
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public @interface CurrentTimeMillisLong {
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/SystemApi.java b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/SystemApi.java
new file mode 100644
index 0000000..678d085
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/SystemApi.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+// @exportToFramework:skipFile()
+package androidx.appsearch.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an API is exposed for use by bundled system applications.
+ *
+ * <p>These APIs are not guaranteed to remain consistent release-to-release,
+ * and are not for use by apps linking against the Android SDK.
+ *
+ * <p>This annotation should only appear on API that is already marked @<pre>hide</pre>.
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
+@Retention(RetentionPolicy.RUNTIME)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public @interface SystemApi {
+ enum Client {
+ /**
+ * Specifies that the intended clients of a SystemApi are privileged apps.
+ * This is the default value for {@link #client}.
+ */
+ PRIVILEGED_APPS,
+
+ /**
+ * Specifies that the intended clients of a SystemApi are used by classes in
+ * <pre>BOOTCLASSPATH</pre> in mainline modules. Mainline modules can also expose
+ * this type of system APIs too when they're used only by the non-updatable
+ * platform code.
+ */
+ MODULE_LIBRARIES,
+
+ /**
+ * Specifies that the system API is available only in the system server process.
+ * Use this to expose APIs from code loaded by the system server process <em>but</em>
+ * not in <pre>BOOTCLASSPATH</pre>.
+ */
+ SYSTEM_SERVER
+ }
+
+ /**
+ * The intended client of this SystemAPI.
+ */
+ Client client() default Client.PRIVILEGED_APPS;
+
+ /**
+ * Container for {@link SystemApi} that allows it to be applied repeatedly to types.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(TYPE)
+ @interface Container {
+ SystemApi[] value();
+ }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchBlobHandle.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchBlobHandle.java
index d6ed1c2..2d6501d 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchBlobHandle.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchBlobHandle.java
@@ -71,7 +71,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Constructor
- private AppSearchBlobHandle(
+ AppSearchBlobHandle(
@Param(id = 1) @NonNull byte[] sha256Digest,
@Param(id = 2) @NonNull String label) {
mSha256Digest = Preconditions.checkNotNull(sha256Digest);
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
index 6de5448..006d119 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
@@ -17,14 +17,19 @@
package androidx.appsearch.app;
import android.annotation.SuppressLint;
+import android.os.Build;
+import android.os.Parcel;
import android.util.Log;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.appsearch.annotation.CanIgnoreReturnValue;
+import androidx.appsearch.annotation.CurrentTimeMillisLong;
import androidx.appsearch.annotation.Document;
+import androidx.appsearch.annotation.SystemApi;
import androidx.appsearch.exceptions.AppSearchException;
import androidx.appsearch.flags.FlaggedApi;
import androidx.appsearch.flags.Flags;
@@ -152,6 +157,45 @@
}
/**
+ * Writes the {@link GenericDocument} to the given {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to write to.
+ * @param flags The flags to use for parceling.
+ * @exportToFramework:hide
+ */
+ // GenericDocument is an open class that can be extended, whereas parcelable classes must be
+ // final in those methods. Thus, we make this a system api to avoid 3p apps depending on it
+ // and getting confused by the inheritability.
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(Flags.FLAG_ENABLE_GENERIC_DOCUMENT_OVER_IPC)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public final void writeToParcel(@NonNull Parcel dest, int flags) {
+ Objects.requireNonNull(dest);
+ dest.writeParcelable(mDocumentParcel, flags);
+ }
+
+ /**
+ * Creates a {@link GenericDocument} from a {@link Parcel}.
+ *
+ * @param parcel The {@link Parcel} to read from.
+ * @exportToFramework:hide
+ */
+ // GenericDocument is an open class that can be extended, whereas parcelable classes must be
+ // final in those methods. Thus, we make this a system api to avoid 3p apps depending on it
+ // and getting confused by the inheritability.
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
+ @FlaggedApi(Flags.FLAG_ENABLE_GENERIC_DOCUMENT_OVER_IPC)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @NonNull
+ public static GenericDocument createFromParcel(@NonNull Parcel parcel) {
+ Objects.requireNonNull(parcel);
+ return new GenericDocument(
+ parcel.readParcelable(
+ GenericDocumentParcel.class.getClassLoader(), GenericDocumentParcel.class));
+ }
+
+ /**
* Returns the {@link GenericDocumentParcel} holding the values for this
* {@link GenericDocument}.
*
@@ -202,7 +246,7 @@
*
* <p>The value is in the {@link System#currentTimeMillis} time base.
*/
- /*@exportToFramework:CurrentTimeMillisLong*/
+ @CurrentTimeMillisLong
public long getCreationTimestampMillis() {
return mDocumentParcel.getCreationTimestampMillis();
}
@@ -1358,7 +1402,7 @@
@CanIgnoreReturnValue
@NonNull
public BuilderType setCreationTimestampMillis(
- /*@exportToFramework:CurrentTimeMillisLong*/ long creationTimestampMillis) {
+ @CurrentTimeMillisLong long creationTimestampMillis) {
mDocumentParcelBuilder.setCreationTimestampMillis(creationTimestampMillis);
return mBuilderTypeInstance;
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/ReportSystemUsageRequest.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/ReportSystemUsageRequest.java
index 67550eb..00957811 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/ReportSystemUsageRequest.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/ReportSystemUsageRequest.java
@@ -18,6 +18,7 @@
import androidx.annotation.NonNull;
import androidx.appsearch.annotation.CanIgnoreReturnValue;
+import androidx.appsearch.annotation.CurrentTimeMillisLong;
import androidx.core.util.Preconditions;
/**
@@ -79,7 +80,7 @@
*
* <p>The value is in the {@link System#currentTimeMillis} time base.
*/
- /*@exportToFramework:CurrentTimeMillisLong*/
+ @CurrentTimeMillisLong
public long getUsageTimestampMillis() {
return mUsageTimestampMillis;
}
@@ -127,7 +128,7 @@
@CanIgnoreReturnValue
@NonNull
public ReportSystemUsageRequest.Builder setUsageTimestampMillis(
- /*@exportToFramework:CurrentTimeMillisLong*/ long usageTimestampMillis) {
+ @CurrentTimeMillisLong long usageTimestampMillis) {
mUsageTimestampMillis = usageTimestampMillis;
return this;
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/ReportUsageRequest.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/ReportUsageRequest.java
index aafcc61..b911919 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/ReportUsageRequest.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/ReportUsageRequest.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.appsearch.annotation.CanIgnoreReturnValue;
+import androidx.appsearch.annotation.CurrentTimeMillisLong;
import androidx.appsearch.flags.FlaggedApi;
import androidx.appsearch.flags.Flags;
import androidx.appsearch.safeparcel.AbstractSafeParcelable;
@@ -84,7 +85,7 @@
*
* <p>The value is in the {@link System#currentTimeMillis} time base.
*/
- /*@exportToFramework:CurrentTimeMillisLong*/
+ @CurrentTimeMillisLong
public long getUsageTimestampMillis() {
return mUsageTimestampMillis;
}
@@ -127,7 +128,7 @@
@CanIgnoreReturnValue
@NonNull
public ReportUsageRequest.Builder setUsageTimestampMillis(
- /*@exportToFramework:CurrentTimeMillisLong*/ long usageTimestampMillis) {
+ @CurrentTimeMillisLong long usageTimestampMillis) {
mUsageTimestampMillis = usageTimestampMillis;
return this;
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java b/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
index 700cfb7..93d8138 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
@@ -80,7 +80,7 @@
* methods.
*/
public static final String FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS =
- FLAG_PREFIX + "enable_search_spec_search_spec_strings";
+ FLAG_PREFIX + "enable_search_spec_search_string_parameters";
/** Enable addTakenActions API in PutDocumentsRequest. */
public static final String FLAG_ENABLE_PUT_DOCUMENTS_REQUEST_ADD_TAKEN_ACTIONS =
@@ -142,6 +142,10 @@
public static final String FLAG_ENABLE_BLOB_STORE =
FLAG_PREFIX + "enable_blob_store";
+ /** Enable {@link androidx.appsearch.app.GenericDocument#writeToParcel}. */
+ public static final String FLAG_ENABLE_GENERIC_DOCUMENT_OVER_IPC =
+ FLAG_PREFIX + "enable_generic_document_over_ipc";
+
/** Enable empty batch result fix for enterprise GetDocuments. */
public static final String FLAG_ENABLE_ENTERPRISE_EMPTY_BATCH_RESULT_FIX =
FLAG_PREFIX + "enable_enterprise_empty_batch_result_fix";
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/GenericDocumentParcel.java b/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/GenericDocumentParcel.java
index 1d407fa..bbfb803 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/GenericDocumentParcel.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/GenericDocumentParcel.java
@@ -24,6 +24,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appsearch.annotation.CanIgnoreReturnValue;
+import androidx.appsearch.annotation.CurrentTimeMillisLong;
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.AppSearchSession;
import androidx.appsearch.app.EmbeddingVector;
@@ -193,7 +194,7 @@
}
/** Returns the creation timestamp of the {@link GenericDocument}, in milliseconds. */
- /*@exportToFramework:CurrentTimeMillisLong*/
+ @CurrentTimeMillisLong
public long getCreationTimestampMillis() {
return mCreationTimestampMillis;
}
@@ -393,7 +394,7 @@
@CanIgnoreReturnValue
@NonNull
public Builder setCreationTimestampMillis(
- /*@exportToFramework:CurrentTimeMillisLong*/ long creationTimestampMillis) {
+ @CurrentTimeMillisLong long creationTimestampMillis) {
mCreationTimestampMillis = creationTimestampMillis;
return this;
}
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.java
index f422d6d..8520b09 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.java
@@ -28,6 +28,7 @@
import com.google.auto.value.AutoValue;
import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.TypeName;
import java.util.Map;
@@ -69,7 +70,8 @@
String name = (String) annotationParams.get("name");
SerializerClass customSerializer = null;
TypeMirror serializerInAnnotation = (TypeMirror) annotationParams.get("serializer");
- if (!serializerInAnnotation.toString().equals(DEFAULT_SERIALIZER_CLASS.canonicalName())) {
+ String typeName = TypeName.get(serializerInAnnotation).toString();
+ if (!typeName.equals(DEFAULT_SERIALIZER_CLASS.canonicalName())) {
customSerializer = SerializerClass.create(
(TypeElement) asElement(serializerInAnnotation),
SerializerClass.Kind.LONG_SERIALIZER);
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.java
index c949c4c..3f086cb 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.java
@@ -28,6 +28,7 @@
import com.google.auto.value.AutoValue;
import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.TypeName;
import java.util.Map;
@@ -69,7 +70,8 @@
String name = (String) annotationParams.get("name");
SerializerClass customSerializer = null;
TypeMirror serializerInAnnotation = (TypeMirror) annotationParams.get("serializer");
- if (!serializerInAnnotation.toString().equals(DEFAULT_SERIALIZER_CLASS.canonicalName())) {
+ String typeName = TypeName.get(serializerInAnnotation).toString();
+ if (!typeName.equals(DEFAULT_SERIALIZER_CLASS.canonicalName())) {
customSerializer = SerializerClass.create(
(TypeElement) asElement(serializerInAnnotation),
SerializerClass.Kind.STRING_SERIALIZER);
diff --git a/appsearch/exportToFramework.py b/appsearch/exportToFramework.py
index b483b72..373cd50 100755
--- a/appsearch/exportToFramework.py
+++ b/appsearch/exportToFramework.py
@@ -32,9 +32,6 @@
# Replaced with @hide:
# <!--@exportToFramework:hide-->
#
-# Replaced with @CurrentTimeMillisLong:
-# /*@exportToFramework:CurrentTimeMillisLong*/
-#
# Removes the text appearing between ifJetpack() and else(), and causes the text appearing between
# else() and --> to become uncommented, to support framework-only Javadocs:
# <!--@exportToFramework:ifJetpack()-->
@@ -143,10 +140,6 @@
# Add additional imports if required
imports_to_add = []
- if '@exportToFramework:CurrentTimeMillisLong' in contents:
- imports_to_add.append('android.annotation.CurrentTimeMillisLong')
- if '@exportToFramework:UnsupportedAppUsage' in contents:
- imports_to_add.append('android.compat.annotation.UnsupportedAppUsage')
for import_to_add in imports_to_add:
contents = re.sub(
r'^(\s*package [^;]+;\s*)$', r'\1\nimport %s;\n' % import_to_add, contents,
@@ -167,6 +160,12 @@
'com.android.server.appsearch.external.localstorage.')
.replace('androidx.appsearch.flags.FlaggedApi', 'android.annotation.FlaggedApi')
.replace('androidx.appsearch.flags.Flags', 'com.android.appsearch.flags.Flags')
+ .replace(
+ 'androidx.appsearch.annotation.CurrentTimeMillis',
+ 'android.annotation.CurrentTimeMillis')
+ .replace(
+ 'androidx.appsearch.annotation.SystemApi',
+ 'android.annotation.SystemApi')
.replace('androidx.appsearch', 'android.app.appsearch')
.replace(
'androidx.annotation.GuardedBy',
@@ -190,9 +189,6 @@
.replace('@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)', '')
.replace('Preconditions.checkNotNull(', 'Objects.requireNonNull(')
.replace('ObjectsCompat.', 'Objects.')
-
- .replace('/*@exportToFramework:CurrentTimeMillisLong*/', '@CurrentTimeMillisLong')
- .replace('/*@exportToFramework:UnsupportedAppUsage*/', '@UnsupportedAppUsage')
.replace('<!--@exportToFramework:hide-->', '@hide')
.replace('@exportToFramework:hide', '@hide')
.replace('// @exportToFramework:skipFile()', '')
diff --git a/benchmark/benchmark-common/build.gradle b/benchmark/benchmark-common/build.gradle
index 6646ae9..70622c4 100644
--- a/benchmark/benchmark-common/build.gradle
+++ b/benchmark/benchmark-common/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import androidx.build.AndroidXConfig
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@@ -94,6 +96,7 @@
inceptionYear = "2018"
description = "Android Benchmark - Common"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
tasks.withType(KotlinCompile).configureEach {
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/AssertsAssert.kt
similarity index 61%
copy from tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt
copy to benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/AssertsAssert.kt
index 3fa6028..81d31ba 100644
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/AssertsAssert.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 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.
@@ -14,9 +14,15 @@
* limitations under the License.
*/
-package androidx.tv.integration.presentation
+package androidx.benchmark
-import android.content.res.AssetManager
+import kotlin.test.Test
+import org.junit.Assert
-fun AssetManager.readAssetsFile(fileName: String): String =
- open(fileName).bufferedReader().use { it.readText() }
+class AssertsAssert {
+ // Makes sure that the test APKs are debuggable and asserts are thrown.
+ @Test
+ fun checkAssertsAreEnforced() {
+ Assert.assertThrows(AssertionError::class.java) { assert(false) }
+ }
+}
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/InMemoryTracingTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/InMemoryTracingTest.kt
index 2a471b2..d1dbc7a 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/InMemoryTracingTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/InMemoryTracingTest.kt
@@ -22,6 +22,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import java.io.File
import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -81,7 +82,7 @@
// verify events
trace.packet[1].apply {
- assert(timestamp in beforeTime..afterTime)
+ assertTrue(timestamp in beforeTime..afterTime)
assertEquals(
TracePacket(
timestamp = timestamp,
@@ -99,7 +100,7 @@
)
}
trace.packet[2].apply {
- assert(timestamp in beforeTime..afterTime)
+ assertTrue(timestamp in beforeTime..afterTime)
assertEquals(
TracePacket(
timestamp = timestamp,
@@ -115,6 +116,92 @@
)
}
}
+
+ @Test
+ fun traceWithCounters() {
+ val beforeTime = 100L
+ val afterTime = 200L
+
+ // test counter embedded in beginSection
+ InMemoryTracing.beginSection(
+ "test trace section",
+ beforeTime,
+ counterNames = listOf("counterLabel"),
+ counterValues = listOf(0.1)
+ )
+ InMemoryTracing.endSection(afterTime)
+
+ // test counter on its own
+ InMemoryTracing.counter("counterLabel", 1.0, afterTime)
+
+ val trace = InMemoryTracing.commitToTrace("testLabel")
+
+ assertEquals(5, trace.packet.size)
+
+ // verify first track, for slices
+ val sliceDescriptor = trace.packet.first().track_descriptor
+ assertNotNull(sliceDescriptor)
+ assertEquals("testLabel", sliceDescriptor.name)
+ // verify second track, for counters
+ val counterDescriptor = trace.packet[1].track_descriptor
+ assertNotNull(counterDescriptor)
+ assertEquals("counterLabel", counterDescriptor.name)
+
+ // verify events
+ trace.packet[2].apply {
+ assertEquals(timestamp, beforeTime)
+ assertEquals(
+ TracePacket(
+ timestamp = timestamp,
+ timestamp_clock_id = 3,
+ trusted_packet_sequence_id = trusted_packet_sequence_id,
+ track_event =
+ TrackEvent(
+ type = TrackEvent.Type.TYPE_SLICE_BEGIN,
+ track_uuid = sliceDescriptor.uuid,
+ categories = listOf("benchmark"),
+ name = "test trace section",
+ extra_double_counter_track_uuids = listOf(counterDescriptor.uuid!!),
+ extra_double_counter_values = listOf(0.1)
+ )
+ ),
+ this
+ )
+ }
+ trace.packet[3].apply {
+ assertEquals(timestamp, afterTime)
+ assertEquals(
+ TracePacket(
+ timestamp = timestamp,
+ timestamp_clock_id = 3,
+ trusted_packet_sequence_id = trusted_packet_sequence_id,
+ track_event =
+ TrackEvent(
+ type = TrackEvent.Type.TYPE_SLICE_END,
+ track_uuid = sliceDescriptor.uuid,
+ )
+ ),
+ this
+ )
+ }
+ trace.packet[4].apply {
+ assertEquals(timestamp, afterTime)
+ assertEquals(
+ TracePacket(
+ timestamp = timestamp,
+ timestamp_clock_id = 3,
+ trusted_packet_sequence_id = trusted_packet_sequence_id,
+ track_event =
+ TrackEvent(
+ type = TrackEvent.Type.TYPE_COUNTER,
+ track_uuid = counterDescriptor.uuid,
+ double_counter_value = 1.0,
+ )
+ ),
+ this
+ )
+ }
+ }
}
@Suppress("SameParameterValue")
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoTraceTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoTraceTest.kt
index e07f881..6068fa7b 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoTraceTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoTraceTest.kt
@@ -52,9 +52,10 @@
// noop
}
assertNotNull(perfettoTrace)
- assert(perfettoTrace!!.path.matches(Regex(".*/testTrace_[0-9-]+.perfetto-trace"))) {
+ assertTrue(
+ perfettoTrace!!.path.matches(Regex(".*/testTrace_[0-9-]+.perfetto-trace")),
"$perfettoTrace didn't match!"
- }
+ )
}
private fun verifyRecordSuccess(config: PerfettoConfig) {
@@ -68,9 +69,10 @@
// noop
}
assertNotNull(perfettoTrace)
- assert(perfettoTrace!!.path.matches(Regex(".*/${label}_[0-9-]+.perfetto-trace"))) {
+ assertTrue(
+ perfettoTrace!!.path.matches(Regex(".*/${label}_[0-9-]+.perfetto-trace")),
"$perfettoTrace didn't match!"
- }
+ )
}
private fun verifyRecordFails(config: PerfettoConfig) {
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
index 3b38d13..9c359f2 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
@@ -76,7 +76,7 @@
tempFile.writeText(fakeText)
ResultWriter.writeReport(tempFile, listOf(reportA, reportB))
- assert(!tempFile.readText().startsWith(fakeText))
+ assertTrue(!tempFile.readText().startsWith(fakeText))
}
@Test
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/InMemoryTracing.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/InMemoryTracing.kt
index a68a4e7..b3e2c64 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/InMemoryTracing.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/InMemoryTracing.kt
@@ -19,6 +19,7 @@
import android.os.Process
import androidx.annotation.RestrictTo
import androidx.benchmark.InMemoryTracing.commitToTrace
+import perfetto.protos.CounterDescriptor
import perfetto.protos.ThreadDescriptor
import perfetto.protos.Trace
import perfetto.protos.TracePacket
@@ -65,18 +66,41 @@
private val TRACK_EVENT_CATEGORIES = listOf("benchmark")
/**
- * For perf/simplicity, this isn't protected by a lock - it should only every be accessed by the
+ * For perf/simplicity, this isn't protected by a lock - it should only ever be accessed by the
* test thread, and dumped/reset between tests.
*/
val events = mutableListOf<TracePacket>()
+ /** Map of counter name to UUID, populated by [counterNameToTrackUuid] */
+ private val counterTracks = mutableMapOf<String, Long>()
+
+ private fun counterNameToTrackUuid(name: String): Long {
+ return counterTracks.getOrPut(name) { UUID + 1 + counterTracks.size }
+ }
+
fun clearEvents() {
events.clear()
+ counterTracks.clear()
}
/** Capture trace state, and return as a Trace(), which can be appended to a trace file. */
fun commitToTrace(label: String): Trace {
val capturedEvents = events.toList()
+ val capturedCounterDescriptors =
+ counterTracks.map { (name, uuid) ->
+ TracePacket(
+ timestamp_clock_id = CLOCK_ID,
+ incremental_state_cleared = true,
+ track_descriptor =
+ TrackDescriptor(
+ uuid = uuid,
+ parent_uuid = UUID,
+ name = name,
+ counter = CounterDescriptor()
+ )
+ )
+ }
+
clearEvents()
return Trace(
listOf(
@@ -94,11 +118,17 @@
disallow_merging_with_system_tracks = true
)
)
- ) + capturedEvents
+ ) + capturedCounterDescriptors + capturedEvents
)
}
- fun beginSection(label: String, nanoTime: Long = System.nanoTime()) {
+ fun beginSection(
+ label: String,
+ nanoTime: Long = System.nanoTime(),
+ counterNames: List<String> = emptyList(),
+ counterValues: List<Double> = emptyList()
+ ) {
+ require(counterNames.size == counterValues.size)
events.add(
TracePacket(
timestamp = nanoTime,
@@ -109,7 +139,10 @@
type = TrackEvent.Type.TYPE_SLICE_BEGIN,
track_uuid = UUID,
categories = TRACK_EVENT_CATEGORIES,
- name = label
+ name = label,
+ extra_double_counter_values = counterValues,
+ extra_double_counter_track_uuids =
+ counterNames.map { counterNameToTrackUuid(it) },
)
)
)
@@ -129,6 +162,23 @@
)
)
}
+
+ fun counter(name: String, value: Double, nanoTime: Long = System.nanoTime()) {
+ events.add(
+ TracePacket(
+ timestamp = nanoTime,
+ timestamp_clock_id = CLOCK_ID,
+ trusted_packet_sequence_id = TRUSTED_PACKET_SEQUENCE_ID,
+ track_event =
+ TrackEvent(
+ type = TrackEvent.Type.TYPE_COUNTER,
+ double_counter_value = value,
+ track_uuid = counterNameToTrackUuid(name),
+ // track_uuid = UUID
+ )
+ )
+ )
+ }
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricsContainer.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricsContainer.kt
index 710ef11..56a5ffe 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricsContainer.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricsContainer.kt
@@ -136,30 +136,42 @@
* Call exactly once at the end of a benchmark.
*/
fun captureFinished(maxIterations: Int): List<MetricResult> {
+ val results =
+ names.mapIndexed { index, name ->
+ val metricData =
+ List(repeatCount) {
+ // convert to floats and divide by iter count here for efficiency
+ data[it][index] / maxIterations.toDouble()
+ }
+ metricData.chunked(10).forEachIndexed { chunkNum, chunk ->
+ Log.d(
+ BenchmarkState.TAG,
+ name +
+ "[%2d:%2d]: %s"
+ .format(
+ chunkNum * 10,
+ (chunkNum + 1) * 10,
+ chunk.joinToString(" ") { it.toLong().toString() }
+ )
+ )
+ }
+ MetricResult(name, metricData)
+ }
+
+ val metricTraceLabels = names.map { "metric: $it" }
for (i in 0..repeatTiming.lastIndex step 2) {
- InMemoryTracing.beginSection("measurement ${i / 2}", nanoTime = repeatTiming[i])
+ val measurementIndex = i / 2
+ InMemoryTracing.beginSection(
+ "measurement $measurementIndex",
+ nanoTime = repeatTiming[i],
+ counterNames = metricTraceLabels,
+ counterValues = results.map { it.data[measurementIndex] }
+ )
InMemoryTracing.endSection(nanoTime = repeatTiming[i + 1])
}
+ // to clarify when measurement ends, reset metrics to 0
+ metricTraceLabels.forEach { InMemoryTracing.counter(it, 0.0, repeatTiming.last()) }
- return names.mapIndexed { index, name ->
- val metricData =
- List(repeatCount) {
- // convert to floats and divide by iter count here for efficiency
- data[it][index] / maxIterations.toDouble()
- }
- metricData.chunked(10).forEachIndexed { chunkNum, chunk ->
- Log.d(
- BenchmarkState.TAG,
- name +
- "[%2d:%2d]: %s"
- .format(
- chunkNum * 10,
- (chunkNum + 1) * 10,
- chunk.joinToString(" ") { it.toLong().toString() }
- )
- )
- }
- MetricResult(name, metricData)
- }
+ return results
}
}
diff --git a/benchmark/benchmark-common/src/main/proto/perfetto_trace.proto b/benchmark/benchmark-common/src/main/proto/perfetto_trace.proto
index 12b204f..5372b59 100644
--- a/benchmark/benchmark-common/src/main/proto/perfetto_trace.proto
+++ b/benchmark/benchmark-common/src/main/proto/perfetto_trace.proto
@@ -94,9 +94,18 @@
enum Type {
TYPE_SLICE_BEGIN = 1;
TYPE_SLICE_END = 2;
+ TYPE_COUNTER = 4;
}
optional Type type = 9;
optional uint64 track_uuid = 11;
+ oneof counter_value_field {
+ int64 counter_value = 30;
+ double double_counter_value = 44;
+ }
+ repeated uint64 extra_counter_track_uuids = 31;
+ repeated int64 extra_counter_values = 12;
+ repeated uint64 extra_double_counter_track_uuids = 45;
+ repeated double extra_double_counter_values = 46;
}
message ThreadDescriptor {
@@ -104,10 +113,15 @@
optional int32 tid = 2;
}
+message CounterDescriptor {
+}
+
message TrackDescriptor {
optional uint64 uuid = 1;
+ optional uint64 parent_uuid = 5;
optional string name = 2;
optional ThreadDescriptor thread = 4;
+ optional CounterDescriptor counter = 8;
optional bool disallow_merging_with_system_tracks = 9;
}
diff --git a/benchmark/benchmark-junit4/build.gradle b/benchmark/benchmark-junit4/build.gradle
index 4a0d751..aafa2a8 100644
--- a/benchmark/benchmark-junit4/build.gradle
+++ b/benchmark/benchmark-junit4/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@@ -63,6 +65,7 @@
inceptionYear = "2019"
description = "Android Benchmark - JUnit4"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
tasks.withType(KotlinCompile).configureEach {
diff --git a/benchmark/benchmark-macro-junit4/build.gradle b/benchmark/benchmark-macro-junit4/build.gradle
index 5b0de69..4f9ef1f 100644
--- a/benchmark/benchmark-macro-junit4/build.gradle
+++ b/benchmark/benchmark-macro-junit4/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@@ -75,4 +77,5 @@
inceptionYear = "2020"
description = "Android Benchmark - Macrobenchmark JUnit4"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/benchmark/benchmark-macro/build.gradle b/benchmark/benchmark-macro/build.gradle
index 3e00f6b..3ffb560 100644
--- a/benchmark/benchmark-macro/build.gradle
+++ b/benchmark/benchmark-macro/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import androidx.build.AndroidXConfig
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@@ -166,7 +168,7 @@
def installTask = tasks.findByPath(
":benchmark:integration-tests:macrobenchmark-target:installRelease")
if (installTask != null) {
- tasks.getByPath(":benchmark:benchmark-macro:connectedDebugAndroidTest")
+ tasks.getByPath(":benchmark:benchmark-macro:connectedReleaseAndroidTest")
.dependsOn(installTask)
}
}
@@ -176,4 +178,5 @@
targetAppProject = project(":benchmark:integration-tests:macrobenchmark-target")
targetAppVariant = "release"
}
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
index 35dab5c..6f8298f 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
@@ -29,6 +29,10 @@
createTempFileFromAsset(prefix = "api24_startup_cold", suffix = ".perfetto-trace")
.absolutePath
+ private val api31ColdStart =
+ createTempFileFromAsset(prefix = "api31_startup_cold", suffix = ".perfetto-trace")
+ .absolutePath
+
private val commasInSliceNames =
createTempFileFromAsset(prefix = "api24_commas_in_slice_names", suffix = ".perfetto-trace")
.absolutePath
@@ -107,6 +111,20 @@
targetPackageOnly = false,
)
+ @Test
+ fun filterNonTerminatingSlices() =
+ verifyFirstSum(
+ tracePath = api31ColdStart, // arbitrary trace which includes non-termination slices
+ packageName = Packages.TARGET, // ignored
+ sectionName = "wait",
+ expectedFirstMs = 0.00724,
+ expectedMinMs = 0.001615, // filtered out non-terminating -1 duration
+ expectedMaxMs = 357.761234,
+ expectedSumMs = 811.865025,
+ expectedSumCount = 226, // filtered out single case where dur = -1
+ targetPackageOnly = false,
+ )
+
companion object {
private fun verifyMetric(
tracePath: String,
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
index 8a2cb2f..519b60e 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
@@ -457,6 +457,9 @@
* )
* ```
*
+ * Note that non-terminating slices in the trace (where duration = -1) are always ignored by this
+ * metric.
+ *
* @see androidx.tracing.Trace.beginSection
* @see androidx.tracing.Trace.endSection
* @see androidx.tracing.trace
@@ -553,10 +556,12 @@
traceSession: PerfettoTraceProcessor.Session
): List<Measurement> {
val slices =
- traceSession.querySlices(
- sectionName,
- packageName = if (targetPackageOnly) captureInfo.targetPackageName else null
- )
+ traceSession
+ .querySlices(
+ sectionName,
+ packageName = if (targetPackageOnly) captureInfo.targetPackageName else null
+ )
+ .filter { it.dur != -1L } // filter out non-terminating slices
return when (mode) {
Mode.First -> {
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MetricResultExtensions.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MetricResultExtensions.kt
index c78134b..9e03d06 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MetricResultExtensions.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MetricResultExtensions.kt
@@ -64,8 +64,8 @@
expectedSamples.zip(observedSamples).forEachIndexed { index, pair ->
if (abs(pair.first - pair.second) > threshold) {
errorString +=
- "$name sample $index observed ${pair.first}" +
- " more than $threshold from expected ${pair.second}\n"
+ "$name sample $index observed ${pair.second}, which is" +
+ " more than $threshold from expected ${pair.first}\n"
}
}
}
diff --git a/benchmark/benchmark/build.gradle b/benchmark/benchmark/build.gradle
index cb95e6d..3c10910 100644
--- a/benchmark/benchmark/build.gradle
+++ b/benchmark/benchmark/build.gradle
@@ -40,6 +40,7 @@
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.junit)
androidTestImplementation(libs.kotlinStdlib)
+ androidTestImplementation(libs.kotlinTest)
}
android {
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/BenchmarkConfigTest.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/BenchmarkConfigTest.kt
new file mode 100644
index 0000000..a350a82
--- /dev/null
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/BenchmarkConfigTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.benchmark
+
+import android.content.pm.ApplicationInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * These tests validate build time properties of benchmarks.
+ *
+ * These tests are enforced in presubmit, even if dryRunMode=true is passed. Standard
+ * microbenchmarks will not perform these checks when dryRunMode=false.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class BenchmarkConfigTest {
+ private val arguments = InstrumentationRegistry.getArguments()
+ private val contextTest = InstrumentationRegistry.getInstrumentation().context
+ private val contextTarget = InstrumentationRegistry.getInstrumentation().targetContext
+
+ @Test
+ fun coverageDisabled() {
+ assertNotEquals(
+ illegal = "true",
+ actual = arguments.getString("coverage"),
+ message = "Coverage must not be enabled in microbench instrumentation args"
+ )
+ }
+
+ @Test
+ fun selfInstrumenting() {
+ assertEquals(
+ expected = contextTest.packageName,
+ actual = contextTarget.packageName,
+ message =
+ "Microbenchmark must be self-instrumenting," +
+ " test pkg=${contextTest.packageName}," +
+ " target pkg=${contextTarget.packageName}"
+ )
+ }
+
+ @Test
+ fun debuggableFalse() {
+ val debuggable = contextTest.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0
+ assertFalse(debuggable, "Microbenchmark must not be debuggable")
+ }
+}
diff --git a/benchmark/gradle-plugin/src/main/resources/scripts/disableJit.sh b/benchmark/gradle-plugin/src/main/resources/scripts/disableJit.sh
new file mode 100755
index 0000000..858a968
--- /dev/null
+++ b/benchmark/gradle-plugin/src/main/resources/scripts/disableJit.sh
@@ -0,0 +1,51 @@
+#
+# Copyright (C) 2024 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.
+#
+
+# ADB push intro copied from lockClocks.sh
+if [ "`command -v getprop`" == "" ]; then
+ if [ -n "`command -v adb`" ]; then
+ echo ""
+ echo "Pushing $0 and running it on device..."
+ dest=/data/local/tmp/`basename $0`
+ adb push $0 ${dest}
+ adb shell ${dest} $@
+ adb shell rm ${dest}
+ exit
+ else
+ echo "Could not find adb. Options are:"
+ echo " 1. Ensure adb is on your \$PATH"
+ echo " 2. Use './gradlew lockClocks'"
+ echo " 3. Manually adb push this script to your device, and run it there"
+ exit -1
+ fi
+fi
+
+echo ""
+
+# require root
+if [[ `id` != "uid=0"* ]]; then
+ echo "Not running as root, cannot disable jit, aborting"
+ exit -1
+fi
+
+setprop dalvik.vm.extra-opts "-Xusejit:false"
+stop
+start
+
+DEVICE=`getprop ro.product.device`
+echo "JIT compilation has been disabled on $DEVICE!"
+echo "Performance will be terrible for almost everything! (except e.g. AOT benchmarks)"
+echo "To reenable it (strongly recommended after benchmarking!!!), reboot or run resetDevice.sh"
diff --git a/benchmark/gradle-plugin/src/main/resources/scripts/resetDevice.sh b/benchmark/gradle-plugin/src/main/resources/scripts/resetDevice.sh
new file mode 100755
index 0000000..060f075
--- /dev/null
+++ b/benchmark/gradle-plugin/src/main/resources/scripts/resetDevice.sh
@@ -0,0 +1,45 @@
+#
+# Copyright (C) 2024 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.
+#
+
+# ADB push intro copied from lockClocks.sh
+if [ "`command -v getprop`" == "" ]; then
+ if [ -n "`command -v adb`" ]; then
+ echo ""
+ echo "Pushing $0 and running it on device..."
+ dest=/data/local/tmp/`basename $0`
+ adb push $0 ${dest}
+ adb shell ${dest} $@
+ # adb shell rm ${dest} # will fail, not very important
+ exit
+ else
+ echo "Could not find adb. Options are:"
+ echo " 1. Ensure adb is on your \$PATH"
+ echo " 2. Use './gradlew lockClocks'"
+ echo " 3. Manually adb push this script to your device, and run it there"
+ exit -1
+ fi
+fi
+
+DEVICE=`getprop ro.product.device`
+echo ""
+echo "Rebooting $DEVICE, and resetting animation scales!"
+echo "This will re-lock clocks, reenable JIT, and reset animation scale to 1.0"
+
+settings put global window_animation_scale 1.0
+settings put global transition_animation_scale 1.0
+settings put global animator_duration_scale 1.0
+
+reboot # required to relock clocks, and handles reenabling jit since the property won't persist
diff --git a/biometric/biometric/src/main/java/androidx/biometric/PromptContentViewWithMoreOptionsButton.java b/biometric/biometric/src/main/java/androidx/biometric/PromptContentViewWithMoreOptionsButton.java
index eb4f9aa..76ce1a1 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/PromptContentViewWithMoreOptionsButton.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/PromptContentViewWithMoreOptionsButton.java
@@ -45,15 +45,12 @@
* .setContentView(
* new PromptContentViewWithMoreOptionsButton.Builder()
* .setDescription("test description")
- * .setMoreOptionsButtonListener(executor, listener)
* .build()
* )
* .build();
* </pre>
*/
public final class PromptContentViewWithMoreOptionsButton implements PromptContentView {
- static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
-
private final String mDescription;
private PromptContentViewWithMoreOptionsButton(@NonNull String description) {
@@ -89,10 +86,6 @@
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@NonNull
public Builder setDescription(@NonNull String description) {
- if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) {
- throw new IllegalArgumentException("The character number of description exceeds "
- + MAX_DESCRIPTION_CHARACTER_NUMBER);
- }
mDescription = description;
return this;
}
diff --git a/biometric/biometric/src/main/java/androidx/biometric/PromptVerticalListContentView.java b/biometric/biometric/src/main/java/androidx/biometric/PromptVerticalListContentView.java
index 58d7be2..b464d05 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/PromptVerticalListContentView.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/PromptVerticalListContentView.java
@@ -42,10 +42,6 @@
* </pre>
*/
public final class PromptVerticalListContentView implements PromptContentView {
- static final int MAX_ITEM_NUMBER = 20;
- static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
- static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
-
private final List<PromptContentItem> mContentList;
private final String mDescription;
@@ -93,10 +89,6 @@
*/
@NonNull
public Builder setDescription(@NonNull String description) {
- if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) {
- throw new IllegalArgumentException("The character number of description exceeds "
- + MAX_DESCRIPTION_CHARACTER_NUMBER);
- }
mDescription = description;
return this;
}
@@ -112,7 +104,6 @@
@NonNull
public Builder addListItem(@NonNull PromptContentItem listItem) {
mContentList.add(listItem);
- checkItemLimits(listItem);
return this;
}
@@ -128,34 +119,9 @@
@NonNull
public Builder addListItem(@NonNull PromptContentItem listItem, int index) {
mContentList.add(index, listItem);
- checkItemLimits(listItem);
return this;
}
- private void checkItemLimits(@NonNull PromptContentItem listItem) {
- if (doesListItemExceedsCharLimit(listItem)) {
- throw new IllegalArgumentException(
- "The character number of list item exceeds "
- + MAX_EACH_ITEM_CHARACTER_NUMBER);
- }
- if (mContentList.size() > MAX_ITEM_NUMBER) {
- throw new IllegalArgumentException(
- "The number of list items exceeds " + MAX_ITEM_NUMBER);
- }
- }
-
- private boolean doesListItemExceedsCharLimit(PromptContentItem listItem) {
- if (listItem instanceof PromptContentItemPlainText) {
- return ((PromptContentItemPlainText) listItem).getText().length()
- > MAX_EACH_ITEM_CHARACTER_NUMBER;
- } else if (listItem instanceof PromptContentItemBulletedText) {
- return ((PromptContentItemBulletedText) listItem).getText().length()
- > MAX_EACH_ITEM_CHARACTER_NUMBER;
- } else {
- return false;
- }
- }
-
/**
* Creates a {@link PromptVerticalListContentView}.
diff --git a/browser/browser/src/main/java/androidx/browser/customtabs/PrefetchOptions.java b/browser/browser/src/main/java/androidx/browser/customtabs/PrefetchOptions.java
index a7699dc..fde9b6f 100644
--- a/browser/browser/src/main/java/androidx/browser/customtabs/PrefetchOptions.java
+++ b/browser/browser/src/main/java/androidx/browser/customtabs/PrefetchOptions.java
@@ -41,7 +41,7 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
PrefetchOptions(
- @NonNull boolean requiresAnonymousIpWhenCrossOrigin, @Nullable Uri sourceOrigin) {
+ boolean requiresAnonymousIpWhenCrossOrigin, @Nullable Uri sourceOrigin) {
this.requiresAnonymousIpWhenCrossOrigin = requiresAnonymousIpWhenCrossOrigin;
this.sourceOrigin = sourceOrigin;
}
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index c2a6cc7..cdf3a164 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -22,6 +22,11 @@
repos.addMavenRepositories(repositories)
+project.tasks.withType(Jar).configureEach { task ->
+ task.reproducibleFileOrder = true
+ task.preserveFileTimestamps = false
+}
+
dependencies {
api(project("plugins"))
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
index 95d88c4..e178e58 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
@@ -143,13 +143,16 @@
* If true, enable the ArrayNullnessMigration lint check to transition to type-use nullness
* annotations. Defaults to false.
*/
-const val MIGRATE_ARRAY_ANNOTATIONS = "androidx.migrateArrayAnnotations"
+const val USE_JSPECIFY_ANNOTATIONS = "androidx.useJSpecifyAnnotations"
/** If true, yarn dependencies are fetched from an offline mirror */
const val YARN_OFFLINE_MODE = "androidx.yarnOfflineMode"
const val FORCE_KOTLIN_2_0_TARGET = "androidx.forceKotlin20Target"
+/** Defined by AndroidX Benchmark Plugin, may be used for local experiments with compilation */
+const val FORCE_BENCHMARK_AOT_COMPILATION = "androidx.benchmark.forceaotcompilation"
+
val ALL_ANDROIDX_PROPERTIES =
setOf(
ADD_GROUP_CONSTRAINTS,
@@ -183,9 +186,10 @@
FilteredAnchorTask.PROP_TASK_NAME,
FilteredAnchorTask.PROP_PATH_PREFIX,
INCLUDE_OPTIONAL_PROJECTS,
- MIGRATE_ARRAY_ANNOTATIONS,
+ USE_JSPECIFY_ANNOTATIONS,
YARN_OFFLINE_MODE,
FORCE_KOTLIN_2_0_TARGET,
+ FORCE_BENCHMARK_AOT_COMPILATION,
) + AndroidConfigImpl.GRADLE_PROPERTIES
fun Project.shouldForceKotlin20Target() =
@@ -285,10 +289,10 @@
findBooleanProperty(ALLOW_CUSTOM_COMPILE_SDK) ?: true
/**
- * Whether to enable the ArrayNullnessMigration lint check for moving nullness annotations on arrays
- * when switching a project to type-use nullness annotations.
+ * Whether to enable the JSpecifyNullnessMigration lint check for moving nullness annotations when
+ * switching a project to the JSpecify type-use nullness annotations.
*/
-fun Project.migrateArrayAnnotations() = findBooleanProperty(MIGRATE_ARRAY_ANNOTATIONS) ?: false
+fun Project.useJSpecifyAnnotations() = findBooleanProperty(USE_JSPECIFY_ANNOTATIONS) ?: false
fun Project.findBooleanProperty(propName: String) = booleanPropertyProvider(propName).get()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index cdbd64e..cd8fc46 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -48,6 +48,7 @@
import com.android.build.api.dsl.KotlinMultiplatformAndroidTestOnJvmCompilation
import com.android.build.api.dsl.LibraryExtension
import com.android.build.api.dsl.PrivacySandboxSdkExtension
+import com.android.build.api.dsl.TestBuildType
import com.android.build.api.dsl.TestExtension
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
@@ -416,6 +417,16 @@
evaluatedProject.kotlinExtensionOrNull?.let { kotlinExtension ->
kotlinExtension.coreLibrariesVersion = kotlinVersionStringProvider.get()
}
+ if (evaluatedProject.androidXExtension.shouldPublish()) {
+ tasks.register(
+ CheckKotlinApiTargetTask.TASK_NAME,
+ CheckKotlinApiTargetTask::class.java
+ ) {
+ it.kotlinTarget.set(kotlinVersionProvider)
+ it.outputFile.set(layout.buildDirectory.file("kotlinApiTargetCheckReport.txt"))
+ }
+ addToBuildOnServer(CheckKotlinApiTargetTask.TASK_NAME)
+ }
}
// Resolve classpath conflicts caused by kotlin-stdlib-jdk7 and -jdk8 artifacts by amending
@@ -822,11 +833,11 @@
}
private fun configureWithLibraryPlugin(project: Project, androidXExtension: AndroidXExtension) {
+ val buildTypeForTests = "release"
project.extensions.getByType<LibraryExtension>().apply {
publishing { singleVariant(DEFAULT_PUBLISH_CONFIG) }
configureAndroidBaseOptions(project, androidXExtension)
-
val debugSigningConfig = signingConfigs.getByName("debug")
// Use a local debug keystore to avoid build server issues.
debugSigningConfig.storeFile = project.getKeystore()
@@ -834,6 +845,7 @@
// Sign all the builds (including release) with debug key
buildType.signingConfig = debugSigningConfig
}
+ testBuildType = buildTypeForTests
project.configureTestConfigGeneration(this)
project.addAppApkToTestConfigGeneration(androidXExtension)
}
@@ -853,10 +865,13 @@
it.defaultConfig.aarMetadata.minCompileSdk = it.compileSdk
it.lint.targetSdk = project.defaultAndroidConfig.targetSdk
it.testOptions.targetSdk = project.defaultAndroidConfig.targetSdk
+ // Replace with a public API once available, see b/360392255
+ it.buildTypes.configureEach { buildType ->
+ if (buildType.name == buildTypeForTests && !project.hasBenchmarkPlugin())
+ (buildType as TestBuildType).isDebuggable = true
+ }
}
- beforeVariants(selector().withBuildType("release")) { variant ->
- (variant as HasUnitTestBuilder).enableUnitTest = false
- }
+ beforeVariants(selector().withBuildType("debug")) { variant -> variant.enable = false }
beforeVariants(selector().all()) { variant ->
variant.androidTest.targetSdk = project.defaultAndroidConfig.targetSdk
}
@@ -940,7 +955,6 @@
projectName != null && projectName.contains("desktop") -> VERSION_11
targetName != null && (targetName == "desktop" || targetName == "jvmStubs") ->
VERSION_11
- libraryType == LibraryType.COMPILER_PLUGIN -> VERSION_11
libraryType.compilationTarget == CompilationTarget.HOST -> VERSION_17
else -> VERSION_1_8
}
@@ -1094,9 +1108,12 @@
}
}
- project.configureFtlRunner(
+ val componentsExtension =
project.extensions.getByType(AndroidComponentsExtension::class.java)
- )
+ project.configureFtlRunner(componentsExtension)
+
+ // If a dependency is missing a debug variant, use release instead.
+ buildTypes.getByName("debug").matchingFallbacks.add("release")
// AGP warns if we use project.buildDir (or subdirs) for CMake's generated
// build files (ninja build files, CMakeCache.txt, etc.). Use a staging directory that
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/CheckKotlinApiTargetTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/CheckKotlinApiTargetTask.kt
new file mode 100644
index 0000000..62dbdd0
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/CheckKotlinApiTargetTask.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.build
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.work.DisableCachingByDefault
+import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
+
+/** Check if the kotlin-stdlib transitive dependencies are the same as the project specified one. */
+@DisableCachingByDefault(because = "not worth caching")
+abstract class CheckKotlinApiTargetTask : DefaultTask() {
+
+ @get:Input abstract val kotlinTarget: Property<KotlinVersion>
+
+ @get:Internal val projectPath: String = project.path
+
+ @get:Input
+ val allDependencies: List<Pair<String, String>> =
+ project.configurations
+ .filter {
+ it.isCanBeResolved &&
+ !it.name.lowercase().contains("test") &&
+ !it.name.lowercase().contains("metadata") &&
+ !it.name.endsWith("CInterop")
+ }
+ .flatMap { config ->
+ config.resolvedConfiguration.firstLevelModuleDependencies.map {
+ "${it.moduleName}:${it.moduleVersion}" to config.name
+ }
+ }
+
+ @get:OutputFile abstract val outputFile: RegularFileProperty
+
+ @TaskAction
+ fun check() {
+ val incompatibleConfigurations =
+ allDependencies
+ .asSequence()
+ .filter { it.first.startsWith("kotlin-stdlib:") }
+ .map { it.first.substringAfter(":") to it.second }
+ .map { KotlinVersion.fromVersion(it.first.substringBeforeLast('.')) to it.second }
+ .filter { it.first != kotlinTarget.get() }
+ .map { it.second }
+ .toList()
+
+ val outputFile = outputFile.get().asFile
+ outputFile.parentFile.mkdirs()
+
+ if (incompatibleConfigurations.isNotEmpty()) {
+ val errorMessage =
+ incompatibleConfigurations.joinToString(
+ prefix =
+ "The project's kotlin-stdlib target is ${kotlinTarget.get()} but these " +
+ "configurations are pulling in different versions of kotlin-stdlib: ",
+ postfix =
+ "\nRun ./gradlew $projectPath:dependencies to see which dependency is " +
+ "pulling in the incompatible kotlin-stdlib"
+ )
+ outputFile.writeText("FAILURE: $errorMessage")
+ throw IllegalStateException(errorMessage)
+ }
+ }
+
+ companion object {
+ const val TASK_NAME = "checkKotlinApiTarget"
+ }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
index 43dd805..005f95f 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
@@ -169,6 +169,9 @@
"-Xep:Finalize:OFF",
"-Xep:AddressSelection:OFF",
"-Xep:StringCharset:OFF",
+ "-Xep:EnumOrdinal:OFF",
+ "-Xep:ClassInitializationDeadlock:OFF",
+ "-Xep:VoidUsed:OFF",
// We allow inter library RestrictTo usage.
"-Xep:RestrictTo:OFF",
@@ -247,6 +250,15 @@
"-Xep:CatchAndPrintStackTrace:ERROR",
"-Xep:MixedMutabilityReturnType:ERROR",
+ // Enforce checks related to nullness annotation usage
+ "-Xep:NullablePrimitiveArray:ERROR",
+ "-Xep:MultipleNullnessAnnotations:ERROR",
+ "-Xep:NullablePrimitive:ERROR",
+ "-Xep:NullableVoid:ERROR",
+ "-Xep:NullableWildcard:ERROR",
+ "-Xep:NullableTypeParameter:ERROR",
+ "-Xep:NullableConstructor:ERROR",
+
// Nullaway
"-XepIgnoreUnknownCheckNames", // https://github.com/uber/NullAway/issues/25
"-Xep:NullAway:ERROR",
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Ktfmt.kt b/buildSrc/private/src/main/kotlin/androidx/build/Ktfmt.kt
index 1b01fa2..202a030 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Ktfmt.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Ktfmt.kt
@@ -19,21 +19,20 @@
import androidx.build.logging.TERMINAL_RED
import androidx.build.logging.TERMINAL_RESET
import androidx.build.uptodatedness.cacheEvenIfNoOutputs
-import com.facebook.ktfmt.format.Formatter
-import com.facebook.ktfmt.format.Formatter.format
+import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.file.Paths
import javax.inject.Inject
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
-import kotlinx.coroutines.awaitAll
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.runBlocking
import org.gradle.api.DefaultTask
import org.gradle.api.Project
+import org.gradle.api.attributes.java.TargetJvmEnvironment
+import org.gradle.api.attributes.java.TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
@@ -43,21 +42,19 @@
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
-import org.intellij.lang.annotations.Language
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import org.gradle.kotlin.dsl.named
+import org.gradle.process.ExecOperations
fun Project.configureKtfmt() {
- tasks.register("ktFormat", KtfmtFormatTask::class.java)
+ val ktfmtClasspath = getKtfmtConfiguration()
+ tasks.register("ktFormat", KtfmtFormatTask::class.java) { task ->
+ task.ktfmtClasspath.from(ktfmtClasspath)
+ }
val ktCheckTask =
tasks.register("ktCheck", KtfmtCheckTask::class.java) { task ->
+ task.ktfmtClasspath.from(ktfmtClasspath)
task.cacheEvenIfNoOutputs()
- // Workaround for https://github.com/gradle/gradle/issues/29205
- // Our ktfmt tasks declare "src" as an input, while our KotlinCompile tasks use
- // something like src/main/java as an input
- // Currently Gradle can sometimes get confused when loading a parent and child directory
- // at the same time, so we ask Gradle to avoid running both tasks in parallel
- task.mustRunAfter(project.tasks.withType(KotlinCompile::class.java))
}
// afterEvaluate because Gradle's default "check" task doesn't exist yet
@@ -78,11 +75,27 @@
)
private val ExcludedDirectoryGlobs = ExcludedDirectories.map { "**/$it/**/*.kt" }
+private const val MainClass = "com.facebook.ktfmt.cli.Main"
private const val InputDir = "src"
private const val IncludedFiles = "**/*.kt"
+private fun Project.getKtfmtConfiguration(): FileCollection {
+ val conf = configurations.detachedConfiguration(dependencies.create(getLibraryByName("ktfmt")))
+ conf.attributes {
+ it.attribute(
+ TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
+ project.objects.named(TargetJvmEnvironment.STANDARD_JVM)
+ )
+ }
+ return files(conf)
+}
+
@CacheableTask
abstract class BaseKtfmtTask : DefaultTask() {
+ @get:Inject abstract val execOperations: ExecOperations
+
+ @get:Classpath abstract val ktfmtClasspath: ConfigurableFileCollection
+
@get:Inject abstract val objects: ObjectFactory
@get:Internal val projectPath: String = project.path
@@ -114,54 +127,39 @@
protected fun runKtfmt(format: Boolean) {
if (getInputFiles().files.isEmpty()) return
- runBlocking(Dispatchers.IO) {
- val result = processInputFiles()
- val incorrectlyFormatted = result.filter { !it.isCorrectlyFormatted }
- if (incorrectlyFormatted.isNotEmpty()) {
- if (format) {
- incorrectlyFormatted.forEach { it.input.writeText(it.formattedCode) }
- } else {
- error(
- "Found ${incorrectlyFormatted.size} files that are not correctly " +
- "formatted:\n" +
- incorrectlyFormatted.map { it.input }.joinToString("\n") +
- """
-
- ********************************************************************************
- You can attempt to automatically fix these issues with:
- ./gradlew $projectPath:ktFormat
- ********************************************************************************
- """
- .trimIndent()
- )
- }
- }
+ val outputStream = ByteArrayOutputStream()
+ execOperations.javaexec { javaExecSpec ->
+ javaExecSpec.standardOutput = outputStream
+ javaExecSpec.mainClass.set(MainClass)
+ javaExecSpec.classpath = ktfmtClasspath
+ javaExecSpec.args = getArgsList(format = format)
+ javaExecSpec.jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
+ overrideDirectory?.let { javaExecSpec.workingDir = it }
+ }
+ val output = outputStream.toString()
+ if (output.isNotEmpty()) {
+ processOutput(output)
+ }
+ if (output.isNotEmpty()) {
+ error(processOutput(output))
}
}
- /** Run ktfmt on all the files in [getInputFiles] in parallel. */
- private suspend fun processInputFiles(): List<KtfmtResult> {
- return coroutineScope { getInputFiles().files.map { async { processFile(it) } }.awaitAll() }
- }
+ open fun processOutput(output: String): String =
+ """
+ Failed check for the following files:
+ $output
+ """
+ .trimIndent()
- /** Run ktfmt on the [input] file. */
- private fun processFile(input: File): KtfmtResult {
- val originCode = input.readText()
- val formattedCode = format(Formatter.KOTLINLANG_FORMAT, originCode)
- return KtfmtResult(
- input = input,
- isCorrectlyFormatted = originCode == formattedCode,
- formattedCode = formattedCode
- )
+ private fun getArgsList(format: Boolean): List<String> {
+ val arguments = mutableListOf("--kotlinlang-style")
+ if (!format) arguments.add("--dry-run")
+ arguments.addAll(getInputFiles().files.map { it.absolutePath })
+ return arguments
}
}
-internal data class KtfmtResult(
- val input: File,
- val isCorrectlyFormatted: Boolean,
- @Language("kotlin") val formattedCode: String,
-)
-
@CacheableTask
abstract class KtfmtFormatTask : BaseKtfmtTask() {
init {
@@ -189,6 +187,18 @@
fun runCheck() {
runKtfmt(format = false)
}
+
+ override fun processOutput(output: String): String =
+ """
+ Failed check for the following files:
+ $output
+
+ ********************************************************************************
+ ${TERMINAL_RED}You can automatically fix these issues with:
+ ./gradlew $projectPath:ktFormat$TERMINAL_RESET
+ ********************************************************************************
+ """
+ .trimIndent()
}
@CacheableTask
@@ -243,33 +253,35 @@
@TaskAction
fun runCheck() {
- try {
- runKtfmt(format = format)
- } catch (e: IllegalStateException) {
- val kotlinFiles =
- files.filter { file ->
- val isKotlinFile = file.endsWith(".kt") || file.endsWith(".ktx")
- val inExcludedDir =
- Paths.get(file).any { subPath ->
- ExcludedDirectories.contains(subPath.toString())
- }
+ runKtfmt(format = format)
+ }
- isKotlinFile && !inExcludedDir
- }
- error(
- """
+ override fun processOutput(output: String): String {
+ val kotlinFiles =
+ files.filter { file ->
+ val isKotlinFile = file.endsWith(".kt") || file.endsWith(".ktx")
+ val inExcludedDir =
+ Paths.get(file).any { subPath ->
+ ExcludedDirectories.contains(subPath.toString())
+ }
- ********************************************************************************
- ${TERMINAL_RED}You can attempt to automatically fix these issues with:
- ./gradlew :ktCheckFile --format ${kotlinFiles.joinToString(separator = " "){ "--file $it" }}$TERMINAL_RESET
- ********************************************************************************
- """
- .trimIndent()
- )
- }
+ isKotlinFile && !inExcludedDir
+ }
+ return """
+ Failed check for the following files:
+ $output
+
+ ********************************************************************************
+ ${TERMINAL_RED}You can attempt to automatically fix these issues with:
+ ./gradlew :ktCheckFile --format ${kotlinFiles.joinToString(separator = " "){ "--file $it" }}$TERMINAL_RESET
+ ********************************************************************************
+ """
+ .trimIndent()
}
}
fun Project.configureKtfmtCheckFile() {
- tasks.register("ktCheckFile", KtfmtCheckFileTask::class.java)
+ tasks.register("ktCheckFile", KtfmtCheckFileTask::class.java) { task ->
+ task.ktfmtClasspath.from(getKtfmtConfiguration())
+ }
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 563931b..25c421d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -341,10 +341,10 @@
disable.add("IllegalExperimentalApiUsage")
}
- // Only allow the ArrayMigration check to be run when opted-in, since this is meant to be
- // run once per project when switching to type-use nullness annotations.
- if (!project.migrateArrayAnnotations()) {
- disable.add("ArrayMigration")
+ // Only allow the JSpecifyNullness check to be run when opted-in, while migrating projects
+ // to use JSpecify annotations.
+ if (!project.useJSpecifyAnnotations()) {
+ disable.add("JSpecifyNullness")
}
fatal.add("UastImplementation") // go/hide-uast-impl
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
index 6efe979..ad04c87 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
@@ -222,10 +222,10 @@
/**
* Finds the main compilation for a source set, usually called 'main' but for android we need to
- * search for 'debug' instead.
+ * search for 'release' instead.
*/
private fun KotlinTarget.mainCompilation() =
- compilations.findByName(MAIN_COMPILATION_NAME) ?: compilations.getByName("debug")
+ compilations.findByName(MAIN_COMPILATION_NAME) ?: compilations.getByName("release")
/**
* Writes a metadata file to the given [metadataFile] location for all multiplatform Kotlin source
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
index ffb4cc8..144e37e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -323,11 +323,11 @@
}
}
- // For library modules we only look at the build type debug. The target app project can be
+ // For library modules we only look at the build type release. The target app project can be
// specified through the androidX extension, through: targetAppProjectForInstrumentationTest
// and targetAppProjectVariantForInstrumentationTest.
extensions.findByType(LibraryAndroidComponentsExtension::class.java)?.apply {
- onVariants(selector().withBuildType("debug")) { variant ->
+ onVariants(selector().withBuildType("release")) { variant ->
val targetAppProject =
androidXExtension.deviceTests.targetAppProject ?: return@onVariants
val targetAppProjectVariant = androidXExtension.deviceTests.targetAppVariant
@@ -616,7 +616,7 @@
multiplatformExtension
?.targets
?.filterIsInstance<KotlinAndroidTarget>()
- ?.mapNotNull { it.compilations.find { it.name == "debugAndroidTest" } }
+ ?.mapNotNull { it.compilations.find { it.name == "releaseAndroidTest" } }
?.flatMap { it.allKotlinSourceSets }
?.mapTo(testSourceFileCollections) { it.kotlin.sourceDirectories }
return testSourceFileCollections
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt b/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
index cfb12d4..7ca3e2e 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
@@ -32,30 +32,31 @@
* LibraryType.checkApi represents whether we enforce API compatibility of the library according to
* our semantic versioning protocol
*
- * The possible values of LibraryType are as follows: PUBLISHED_LIBRARY: a conventional library,
- * published, sourced, documented, and versioned. PUBLISHED_TEST_LIBRARY: PUBLISHED_LIBRARY, but
- * allows calling @VisibleForTesting API. Used for libraries that allow developers to test code that
- * uses your library. Often provides test fakes. INTERNAL_TEST_LIBRARY: unpublished, untracked,
- * undocumented. Used in internal tests. Usually contains integration tests, but is _not_ an app.
- * Runs device tests. INTERNAL_HOST_TEST_LIBRARY: as INTERNAL_TEST_LIBRARY, but runs host tests
- * instead. Avoid mixing host tests and device tests in the same library, for performance /
- * test-result-caching reasons. SAMPLES: a library containing sample code referenced in your
- * library's documentation with @sampled, published as a documentation-related supplement to a
- * conventional library. LINT: a library of lint rules for using a conventional library. Published
- * through lintPublish as part of an AAR, not published standalone. COMPILER_DAEMON: a tool that
- * modifies the kotlin or java compiler. Used only while compiling. Has no API and does not publish
- * source jars, but does release to maven. COMPILER_DAEMON_TEST: a compiler plugin that is not
- * published at all, for internal-only use. COMPILER_PLUGIN: as COMPILER_DAEMON, but is compatible
- * with JDK 11. GRADLE_PLUGIN: a library that is a gradle plugin. ANNOTATION_PROCESSOR: a library
- * consisting of an annotation processor. Used only while compiling. ANNOTATION_PROCESSOR_UTILS:
- * contains reference code for understanding an annotation processor. Publishes source jars, but
- * does not track API. OTHER_CODE_PROCESSOR: a library that algorithmically generates and/or alters
- * code but not through hooking into custom annotations or the kotlin compiler. For example,
- * navigation:safe-args-generator or Jetifier. IDE_PLUGIN: a library that should only ever be
- * downloaded by studio. Unfortunately, we don't yet have a good way to track API for these.
- * b/281843422 UNSET: a library that has not yet been migrated to using LibraryType. Should never be
- * used. APP: an app, such as an example app or integration testsapp. Should never be used; apps
- * should not apply the AndroidX plugin or have an androidx block in their build.gradle files.
+ * The possible values of LibraryType are as follows:
+ * - [PUBLISHED_LIBRARY]: a conventional library published, sourced, documented, and versioned.
+ * - [PUBLISHED_TEST_LIBRARY]: [PUBLISHED_LIBRARY], but allows calling `@VisibleForTesting` API.
+ * Used for libraries that allow developers to test code that uses your library. Often provides
+ * test fakes.
+ * - [INTERNAL_TEST_LIBRARY]: unpublished, untracked, undocumented. Used in internal tests. Usually
+ * contains integration tests, but is _not_ an app. Runs device tests.
+ * - [INTERNAL_HOST_TEST_LIBRARY]: as [INTERNAL_TEST_LIBRARY], but runs host tests instead. Avoid
+ * mixing host tests and device tests in the same library, for performance / test-result-caching
+ * reasons.
+ * - [SAMPLES]: a library containing sample code referenced in your library's documentation with
+ * `@sampled`, published as a documentation-related supplement to a conventional library.
+ * - [LINT]: a library of lint rules for using a conventional library. Published through lintPublish
+ * as part of an AAR, not published standalone.
+ * - [GRADLE_PLUGIN]: a library that is a gradle plugin.
+ * - [ANNOTATION_PROCESSOR]: a library consisting of an annotation processor. Used only while
+ * compiling.
+ * - [ANNOTATION_PROCESSOR_UTILS]: contains reference code for understanding an annotation
+ * processor. Publishes source jars, but does not track API.
+ * - [OTHER_CODE_PROCESSOR]: a library that algorithmically generates and/or alters code but not
+ * through hooking into custom annotations or the kotlin compiler. For example,
+ * navigation:safe-args-generator or Jetifier.
+ * - [IDE_PLUGIN]: a library that should only ever be downloaded by studio. Unfortunately, we don't
+ * yet have a good way to track API for these. b/281843422
+ * - [UNSET]: a library that has not yet been migrated to using LibraryType. Should never be used.
*/
sealed class LibraryType(
val publish: Publish = Publish.NONE,
@@ -69,29 +70,25 @@
get() = javaClass.simpleName
companion object {
- val PUBLISHED_LIBRARY = PublishedLibrary()
+ @JvmStatic val ANNOTATION_PROCESSOR = AnnotationProcessor()
+ @JvmStatic val ANNOTATION_PROCESSOR_UTILS = AnnotationProcessorUtils()
+ @JvmStatic val GRADLE_PLUGIN = GradlePlugin()
+ @JvmStatic val IDE_PLUGIN = IdePlugin()
+ @JvmStatic val INTERNAL_TEST_LIBRARY = InternalTestLibrary()
+ @JvmStatic val INTERNAL_HOST_TEST_LIBRARY = InternalHostTestLibrary()
+ @JvmStatic val LINT = Lint()
+ @JvmStatic val PUBLISHED_LIBRARY = PublishedLibrary()
+ @JvmStatic
val PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS =
PublishedLibrary(targetsKotlinConsumersOnly = true)
- val PUBLISHED_TEST_LIBRARY = PublishedTestLibrary()
+ @JvmStatic val PUBLISHED_TEST_LIBRARY = PublishedTestLibrary()
+ @JvmStatic
val PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY =
PublishedTestLibrary(targetsKotlinConsumersOnly = true)
- val INTERNAL_TEST_LIBRARY = InternalTestLibrary()
- val INTERNAL_HOST_TEST_LIBRARY = InternalHostTestLibrary()
- val SAMPLES = Samples()
- val LINT = Lint()
- val COMPILER_DAEMON = CompilerDaemon()
- val COMPILER_DAEMON_TEST = CompilerDaemonTest()
- val COMPILER_PLUGIN = CompilerPlugin()
- val GRADLE_PLUGIN = GradlePlugin()
- val ANNOTATION_PROCESSOR = AnnotationProcessor()
- val ANNOTATION_PROCESSOR_UTILS = AnnotationProcessorUtils()
- val OTHER_CODE_PROCESSOR = OtherCodeProcessor()
- val IDE_PLUGIN = IdePlugin()
+ @JvmStatic val SAMPLES = Samples()
+ @JvmStatic val OTHER_CODE_PROCESSOR = OtherCodeProcessor()
val UNSET = Unset()
- @Deprecated("Do not use an androidx block for apps/testapps, only for libraries")
- val APP = UNSET
- @Suppress("DEPRECATION")
private val allTypes =
mapOf(
"PUBLISHED_LIBRARY" to PUBLISHED_LIBRARY,
@@ -103,16 +100,12 @@
"INTERNAL_HOST_TEST_LIBRARY" to INTERNAL_HOST_TEST_LIBRARY,
"SAMPLES" to SAMPLES,
"LINT" to LINT,
- "COMPILER_DAEMON" to COMPILER_DAEMON,
- "COMPILER_DAEMON_TEST" to COMPILER_DAEMON_TEST,
- "COMPILER_PLUGIN" to COMPILER_PLUGIN,
"GRADLE_PLUGIN" to GRADLE_PLUGIN,
"ANNOTATION_PROCESSOR" to ANNOTATION_PROCESSOR,
"ANNOTATION_PROCESSOR_UTILS" to ANNOTATION_PROCESSOR_UTILS,
"OTHER_CODE_PROCESSOR" to OTHER_CODE_PROCESSOR,
"IDE_PLUGIN" to IDE_PLUGIN,
"UNSET" to UNSET,
- "APP" to APP
)
fun valueOf(name: String): LibraryType {
diff --git a/buildSrc/shared-dependencies.gradle b/buildSrc/shared-dependencies.gradle
index 356e5ef..bc37d9f 100644
--- a/buildSrc/shared-dependencies.gradle
+++ b/buildSrc/shared-dependencies.gradle
@@ -39,8 +39,6 @@
}
implementation(libs.xerces)
- implementation(libs.ktfmt)
- implementation(libs.kotlinCoroutinesCore)
implementation(libs.shadow) // used by BundleInsideHelper.kt
api(libs.apacheAnt) // used in AarManifestTransformerTask.kt for unziping
implementation(libs.toml)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
index 7259c36..26930dd 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
@@ -129,10 +129,10 @@
if (dynamicRangeResolver.is10BitDynamicRangeSupported()) {
generate10BitSupportedCombinationList()
+ }
- if (isUltraHdrSupported()) {
- generateUltraHdrSupportedCombinationList()
- }
+ if (isUltraHdrSupported()) {
+ generateUltraHdrSupportedCombinationList()
}
if (isPreviewStabilizationSupported) {
@@ -194,7 +194,12 @@
return featureSettingsToSupportedCombinationsMap[featureSettings]!!
}
var supportedSurfaceCombinations: MutableList<SurfaceCombination> = mutableListOf()
- if (featureSettings.requiredMaxBitDepth == DynamicRange.BIT_DEPTH_8_BIT) {
+ if (featureSettings.isUltraHdrOn) {
+ // For Ultra HDR output, only the default camera mode is currently supported.
+ if (featureSettings.cameraMode == CameraMode.DEFAULT) {
+ supportedSurfaceCombinations.addAll(surfaceCombinationsUltraHdr)
+ }
+ } else if (featureSettings.requiredMaxBitDepth == DynamicRange.BIT_DEPTH_8_BIT) {
when (featureSettings.cameraMode) {
CameraMode.CONCURRENT_CAMERA ->
supportedSurfaceCombinations = concurrentSurfaceCombinations
@@ -213,11 +218,7 @@
} else if (featureSettings.requiredMaxBitDepth == DynamicRange.BIT_DEPTH_10_BIT) {
// For 10-bit outputs, only the default camera mode is currently supported.
if (featureSettings.cameraMode == CameraMode.DEFAULT) {
- if (featureSettings.isUltraHdrOn) {
- supportedSurfaceCombinations.addAll(surfaceCombinationsUltraHdr)
- } else {
- supportedSurfaceCombinations.addAll(surfaceCombinations10Bit)
- }
+ supportedSurfaceCombinations.addAll(surfaceCombinations10Bit)
}
}
featureSettingsToSupportedCombinationsMap[featureSettings] = supportedSurfaceCombinations
@@ -393,6 +394,11 @@
isPreviewStabilizationOn: Boolean,
isUltraHdrOn: Boolean
): FeatureSettings {
+ require(!(cameraMode != CameraMode.DEFAULT && isUltraHdrOn)) {
+ "Camera device Id is $cameraId. Ultra HDR is not " +
+ "currently supported in ${CameraMode.toLabelString(cameraMode)} camera mode."
+ }
+
val requiredMaxBitDepth = getRequiredMaxBitDepth(resolvedDynamicRanges)
require(
!(cameraMode != CameraMode.DEFAULT &&
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
index 577e4db..83c1aa3 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
@@ -18,6 +18,7 @@
import androidx.camera.camera2.pipe.integration.compat.workaround.AutoFlashAEModeDisabler
import androidx.camera.camera2.pipe.integration.compat.workaround.InactiveSurfaceCloser
+import androidx.camera.camera2.pipe.integration.compat.workaround.Lock3ABehaviorWhenCaptureImage
import androidx.camera.camera2.pipe.integration.compat.workaround.MeteringRegionCorrection
import androidx.camera.camera2.pipe.integration.compat.workaround.TemplateParamsOverride
import androidx.camera.camera2.pipe.integration.compat.workaround.UseFlashModeTorchFor3aUpdate
@@ -34,6 +35,7 @@
UseFlashModeTorchFor3aUpdate.Bindings::class,
UseTorchAsFlash.Bindings::class,
TemplateParamsOverride.Bindings::class,
+ Lock3ABehaviorWhenCaptureImage.Bindings::class,
],
)
public abstract class CameraCompatModule
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt
index ec7f41f..7aa48d6 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt
@@ -19,14 +19,26 @@
import android.graphics.SurfaceTexture
import android.hardware.camera2.params.StreamConfigurationMap
import android.util.Size
+import androidx.camera.core.Logger
import androidx.camera.core.impl.ImageFormatConstants
+private const val TAG = "StreamConfigurationMapCompatBaseImpl"
+
internal open class StreamConfigurationMapCompatBaseImpl(
val streamConfigurationMap: StreamConfigurationMap?
) : StreamConfigurationMapCompat.StreamConfigurationMapCompatImpl {
override fun getOutputFormats(): Array<Int>? {
- return streamConfigurationMap?.outputFormats?.toTypedArray()
+ // b/361590210: try-catch to workaround the NullPointerException issue when using
+ // StreamConfigurationMap provided by Robolectric.
+ val outputFormats =
+ try {
+ streamConfigurationMap?.outputFormats
+ } catch (e: NullPointerException) {
+ Logger.e(TAG, "Failed to get output formats from StreamConfigurationMap", e)
+ null
+ }
+ return outputFormats?.toTypedArray()
}
override fun getOutputSizes(format: Int): Array<Size>? {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
index 7988db32..81e63e1 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
@@ -240,6 +240,14 @@
) {
quirks.add(ImageCaptureFailedForVideoSnapshotQuirk())
}
+ if (
+ quirkSettings.shouldEnableQuirk(
+ LockAeAndCaptureImageBreakCameraQuirk::class.java,
+ LockAeAndCaptureImageBreakCameraQuirk.isEnabled(cameraMetadata)
+ )
+ ) {
+ quirks.add(LockAeAndCaptureImageBreakCameraQuirk())
+ }
Quirks(quirks).also {
Logger.d(TAG, "camera2-pipe-integration CameraQuirks = " + Quirks.toString(it))
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/LockAeAndCaptureImageBreakCameraQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/LockAeAndCaptureImageBreakCameraQuirk.kt
new file mode 100644
index 0000000..95ba980
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/LockAeAndCaptureImageBreakCameraQuirk.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat.quirk
+
+import android.annotation.SuppressLint
+import android.hardware.camera2.CameraCharacteristics.LENS_FACING
+import android.hardware.camera2.CameraMetadata.LENS_FACING_BACK
+import android.os.Build
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.integration.compat.workaround.Lock3ABehaviorWhenCaptureImage
+import androidx.camera.core.impl.Quirk
+
+/**
+ * QuirkSummary
+ * - Bug Id: b/360106037
+ * - Description: Quirk indicating that locking AE (Auto Exposure) and taking pictures can lead to
+ * an abnormal camera service state on Pixel 3 back camera. Although the picture is successfully
+ * taken, the camera service becomes unresponsive without any error callbacks. Reopening the
+ * camera can restore its functionality.
+ * - Device(s): Pixel 3.
+ *
+ * @see Lock3ABehaviorWhenCaptureImage
+ */
+@SuppressLint("CameraXQuirksClassDetector")
+public class LockAeAndCaptureImageBreakCameraQuirk : Quirk {
+
+ public companion object {
+ public fun isEnabled(cameraMetadata: CameraMetadata): Boolean {
+ return isPixel3 && cameraMetadata[LENS_FACING] == LENS_FACING_BACK
+ }
+
+ private val isPixel3: Boolean
+ get() = "Pixel 3".equals(Build.MODEL, ignoreCase = true)
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ZslDisablerQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ZslDisablerQuirk.kt
index 40f7e51..cad386b 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ZslDisablerQuirk.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ZslDisablerQuirk.kt
@@ -24,9 +24,10 @@
/**
* QuirkSummary
- * - Bug Id: 252818931, 261744070, 319913852
- * - Description: On certain devices, the captured image has color issue for reprocessing. We need
- * to disable zero-shutter lag and return false for [CameraInfo.isZslSupported].
+ * - Bug Id: 252818931, 261744070, 319913852, 361328838
+ * - Description: On certain devices, the captured image has color or zoom freezing issue for
+ * reprocessing. We need to disable zero-shutter lag and return false for
+ * [CameraInfo.isZslSupported].
* - Device(s): Samsung Fold4, Samsung s22, Xiaomi Mi 8
*/
@SuppressLint("CameraXQuirksClassDetector")
@@ -34,7 +35,8 @@
public class ZslDisablerQuirk : Quirk {
public companion object {
- private val AFFECTED_SAMSUNG_MODEL = listOf("SM-F936", "SM-S901U", "SM-S908U", "SM-S908U1")
+ private val AFFECTED_SAMSUNG_MODEL =
+ listOf("SM-F936", "SM-S901U", "SM-S908U", "SM-S908U1", "SM-F721U1", "SM-S928U1")
private val AFFECTED_XIAOMI_MODEL = listOf("MI 8")
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/Lock3ABehaviorWhenCaptureImage.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/Lock3ABehaviorWhenCaptureImage.kt
new file mode 100644
index 0000000..f893efd
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/Lock3ABehaviorWhenCaptureImage.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat.workaround
+
+import androidx.camera.camera2.pipe.Lock3ABehavior
+import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
+import androidx.camera.camera2.pipe.integration.compat.quirk.LockAeAndCaptureImageBreakCameraQuirk
+import dagger.Module
+import dagger.Provides
+
+/**
+ * Provides customized 3A lock behaviors before capturing an image.
+ *
+ * @property hasAeLockBehavior Indicates whether there is a specific AE (Auto Exposure) lock
+ * behavior defined. If true, the [aeLockBehavior] will be used; otherwise, the default AE
+ * behavior will be applied.
+ * @property aeLockBehavior The specific AE lock behavior to apply if [hasAeLockBehavior] is true.
+ * If null and [hasAeLockBehavior] is true, AE lock will be effectively disabled.
+ * @property hasAfLockBehavior Indicates whether there is a specific AF (Auto Focus) lock behavior
+ * defined. If true, the [afLockBehavior] will be used; otherwise, the default AF behavior will be
+ * applied.
+ * @property afLockBehavior The specific AF lock behavior to apply if [hasAfLockBehavior] is true.
+ * If null and [hasAfLockBehavior] is true, AF lock will be effectively disabled.
+ * @property hasAwbLockBehavior Indicates whether there is a specific AWB (Auto White Balance) lock
+ * behavior defined. If true, the [awbLockBehavior] will be used; otherwise, the default AWB
+ * behavior will be applied.
+ * @property awbLockBehavior The specific AWB lock behavior to apply if [hasAwbLockBehavior] is
+ * true. If null and [hasAwbLockBehavior] is true, AWB lock will be effectively disabled.
+ * @see LockAeAndCaptureImageBreakCameraQuirk
+ */
+public class Lock3ABehaviorWhenCaptureImage(
+ private val hasAeLockBehavior: Boolean = false,
+ private val aeLockBehavior: Lock3ABehavior? = null,
+ private val hasAfLockBehavior: Boolean = false,
+ private val afLockBehavior: Lock3ABehavior? = null,
+ private val hasAwbLockBehavior: Boolean = false,
+ private val awbLockBehavior: Lock3ABehavior? = null,
+) {
+
+ /**
+ * Gets customized 3A lock behaviors, using provided defaults if no specific behavior is set.
+ *
+ * This method checks the `has*LockBehavior` properties to determine if a custom behavior is
+ * defined for each 3A lock type (AE, AF, AWB). If a custom behavior is defined, it will be
+ * returned; otherwise, the corresponding `default*Behavior` will be used.
+ *
+ * @param defaultAeBehavior Default AE lock behavior if none is specified.
+ * @param defaultAfBehavior Default AF lock behavior if none is specified.
+ * @param defaultAwbBehavior Default AWB lock behavior if none is specified.
+ * @return A Triple containing the customized AE, AF, and AWB lock behaviors.
+ */
+ public fun getLock3ABehaviors(
+ defaultAeBehavior: Lock3ABehavior? = null,
+ defaultAfBehavior: Lock3ABehavior? = null,
+ defaultAwbBehavior: Lock3ABehavior? = null
+ ): Triple<Lock3ABehavior?, Lock3ABehavior?, Lock3ABehavior?> =
+ Triple(
+ if (hasAeLockBehavior) aeLockBehavior else defaultAeBehavior,
+ if (hasAfLockBehavior) afLockBehavior else defaultAfBehavior,
+ if (hasAwbLockBehavior) awbLockBehavior else defaultAwbBehavior
+ )
+
+ @Module
+ public abstract class Bindings {
+ public companion object {
+ @Provides
+ public fun provideLock3ABehaviorBeforeCaptureImage(
+ cameraQuirks: CameraQuirks
+ ): Lock3ABehaviorWhenCaptureImage =
+ if (
+ cameraQuirks.quirks.contains(LockAeAndCaptureImageBreakCameraQuirk::class.java)
+ ) {
+ doNotLockAe3ABehavior
+ } else {
+ noCustomizedLock3ABehavior
+ }
+ }
+ }
+
+ public companion object {
+ public val noCustomizedLock3ABehavior: Lock3ABehaviorWhenCaptureImage by lazy {
+ Lock3ABehaviorWhenCaptureImage()
+ }
+
+ public val doNotLockAe3ABehavior: Lock3ABehaviorWhenCaptureImage by lazy {
+ Lock3ABehaviorWhenCaptureImage(
+ hasAeLockBehavior = true,
+ aeLockBehavior = null // Explicitly disable AE lock
+ )
+ }
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
index 372696c..1d21a90 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
@@ -49,6 +49,7 @@
import androidx.camera.camera2.pipe.core.Log.debug
import androidx.camera.camera2.pipe.core.Log.info
import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
+import androidx.camera.camera2.pipe.integration.compat.workaround.Lock3ABehaviorWhenCaptureImage
import androidx.camera.camera2.pipe.integration.compat.workaround.UseTorchAsFlash
import androidx.camera.camera2.pipe.integration.compat.workaround.isFlashAvailable
import androidx.camera.camera2.pipe.integration.compat.workaround.shouldStopRepeatingBeforeCapture
@@ -110,6 +111,7 @@
private val threads: UseCaseThreads,
private val requestListener: ComboRequestListener,
private val useTorchAsFlash: UseTorchAsFlash,
+ private val lock3ABehaviorWhenCaptureImage: Lock3ABehaviorWhenCaptureImage,
cameraProperties: CameraProperties,
private val useCaseCameraState: UseCaseCameraState,
useCaseGraphConfig: UseCaseGraphConfig,
@@ -378,10 +380,16 @@
graph
.acquireSession()
.use {
+ val (aeLockBehavior, afLockBehavior, awbLockBehavior) =
+ lock3ABehaviorWhenCaptureImage.getLock3ABehaviors(
+ defaultAeBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN,
+ defaultAfBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN,
+ defaultAwbBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN,
+ )
it.lock3A(
- aeLockBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN,
- afLockBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN,
- awbLockBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN,
+ aeLockBehavior = aeLockBehavior,
+ afLockBehavior = afLockBehavior,
+ awbLockBehavior = awbLockBehavior,
convergedTimeLimitNs = convergedTimeLimitNs,
lockedTimeLimitNs = CHECK_3A_TIMEOUT_IN_NS
)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
index de128bc..0731bda 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
@@ -354,9 +354,9 @@
runIfNotClosed {
useGraphSessionOrFailed {
it.update3A(
- aeRegions = METERING_REGIONS_DEFAULT.asList(),
- afRegions = METERING_REGIONS_DEFAULT.asList(),
- awbRegions = METERING_REGIONS_DEFAULT.asList()
+ aeRegions = aeRegions ?: METERING_REGIONS_DEFAULT.asList(),
+ afRegions = afRegions ?: METERING_REGIONS_DEFAULT.asList(),
+ awbRegions = awbRegions ?: METERING_REGIONS_DEFAULT.asList()
)
}
} ?: submitFailedResult
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
index 92be8fb..5d25a6d 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
@@ -1655,16 +1655,34 @@
}
}
+ @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun checkUltraHdrCombinationsSupported_when8bit() {
+ // Device might support Ultra HDR but not 10-bit.
+ setupCamera(supportedFormats = intArrayOf(JPEG_R))
+ val supportedSurfaceCombination =
+ SupportedSurfaceCombination(context, fakeCameraMetadata, mockEncoderProfilesAdapter)
+
+ GuaranteedConfigurationsUtil.getUltraHdrSupportedCombinationList().forEach {
+ assertThat(
+ supportedSurfaceCombination.checkSupported(
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ requiredMaxBitDepth = DynamicRange.BIT_DEPTH_8_BIT,
+ isUltraHdrOn = true
+ ),
+ it.surfaceConfigList
+ )
+ )
+ .isTrue()
+ }
+ }
+
/** JPEG_R/MAXIMUM when Ultra HDR is ON. */
@Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
fun canSelectCorrectSizes_onlyJpegr_whenUltraHdrIsOn() {
- val jpegUseCase =
- createUseCase(
- CaptureType.IMAGE_CAPTURE,
- dynamicRange = HLG_10_BIT,
- imageFormat = JPEG_R
- ) // JPEG
+ val jpegUseCase = createUseCase(CaptureType.IMAGE_CAPTURE, imageFormat = JPEG_R) // JPEG
val useCaseExpectedResultMap =
mutableMapOf<UseCase, Size>().apply { put(jpegUseCase, maximumSize) }
getSuggestedSpecsAndVerify(
@@ -1683,7 +1701,27 @@
val jpegUseCase =
createUseCase(
CaptureType.IMAGE_CAPTURE,
- dynamicRange = HLG_10_BIT,
+ imageFormat = JPEG_R,
+ ) // JPEG
+ val useCaseExpectedResultMap =
+ mutableMapOf<UseCase, Size>().apply {
+ put(privUseCase, previewSize)
+ put(jpegUseCase, maximumSize)
+ }
+ getSuggestedSpecsAndVerify(
+ useCasesExpectedResultMap = useCaseExpectedResultMap,
+ supportedOutputFormats = intArrayOf(JPEG_R),
+ )
+ }
+
+ /** HLG10 PRIV/PREVIEW + JPEG_R/MAXIMUM when Ultra HDR is ON. */
+ @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun canSelectCorrectSizes_hlg10PrivPlusJpegr_whenUltraHdrIsOn() {
+ val privUseCase = createUseCase(CaptureType.PREVIEW, dynamicRange = HLG_10_BIT) // PRIV
+ val jpegUseCase =
+ createUseCase(
+ CaptureType.IMAGE_CAPTURE,
imageFormat = JPEG_R,
) // JPEG
val useCaseExpectedResultMap =
@@ -1708,7 +1746,6 @@
val jpegUseCase =
createUseCase(
CaptureType.IMAGE_CAPTURE,
- dynamicRange = HLG_10_BIT,
imageFormat = JPEG_R,
) // JPEG
val useCaseExpectedResultMap =
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatTest.kt
index 3e8cf9f..4b07606 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatTest.kt
@@ -98,4 +98,22 @@
assertThat(streamConfigurationMapCompat.getHighResolutionOutputSizes(ImageFormat.JPEG))
.isNull()
}
+
+ @Test
+ fun getOutputFormats_notThrowingNullPointerException() {
+ val builder = StreamConfigurationMapBuilder.newBuilder()
+ val compat =
+ StreamConfigurationMapCompat(
+ builder.build(),
+ OutputSizesCorrector(FakeCameraMetadata(), builder.build())
+ )
+
+ // b/361590210: check the workaround for NullPointerException issue (on API 23+) of
+ // StreamConfigurationMap provided by Robolectric is applied.
+ if (Build.VERSION.SDK_INT >= 23) {
+ assertThat(compat.getOutputFormats()).isNull()
+ } else {
+ assertThat(compat.getOutputFormats()).isNotNull()
+ }
+ }
}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/Lock3ABehaviorWhenCaptureImageTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/Lock3ABehaviorWhenCaptureImageTest.kt
new file mode 100644
index 0000000..206bca8
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/Lock3ABehaviorWhenCaptureImageTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat.workaround
+
+import androidx.camera.camera2.pipe.Lock3ABehavior
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+class Lock3ABehaviorWhenCaptureImageTest {
+
+ @Test
+ fun getLock3ABehaviors_noCustomBehaviors_returnsDefaults() {
+ val lock3ABehavior = Lock3ABehaviorWhenCaptureImage()
+ val defaultAeBehavior = null
+ val defaultAfBehavior = Lock3ABehavior.AFTER_NEW_SCAN
+ val defaultAwbBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN
+
+ val (ae, af, awb) =
+ lock3ABehavior.getLock3ABehaviors(
+ defaultAeBehavior,
+ defaultAfBehavior,
+ defaultAwbBehavior
+ )
+
+ assertThat(ae).isEqualTo(defaultAeBehavior)
+ assertThat(af).isEqualTo(defaultAfBehavior)
+ assertThat(awb).isEqualTo(defaultAwbBehavior)
+ }
+
+ @Test
+ fun getLock3ABehaviors_withCustomBehaviors_returnsCustom() {
+ val customAeBehavior = null
+ val customAfBehavior = Lock3ABehavior.AFTER_NEW_SCAN
+ val customAwbBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN
+ val lock3ABehavior =
+ Lock3ABehaviorWhenCaptureImage(
+ hasAeLockBehavior = true,
+ aeLockBehavior = customAeBehavior,
+ hasAfLockBehavior = true,
+ afLockBehavior = customAfBehavior,
+ hasAwbLockBehavior = true,
+ awbLockBehavior = customAwbBehavior
+ )
+
+ val (ae, af, awb) = lock3ABehavior.getLock3ABehaviors() // No defaults needed
+
+ assertThat(ae).isEqualTo(customAeBehavior)
+ assertThat(af).isEqualTo(customAfBehavior)
+ assertThat(awb).isEqualTo(customAwbBehavior)
+ }
+
+ @Test
+ fun getLock3ABehaviors_mixedBehaviors_returnsCorrectly() {
+ val customAfBehavior = Lock3ABehavior.AFTER_NEW_SCAN
+ val lock3ABehavior =
+ Lock3ABehaviorWhenCaptureImage(
+ hasAfLockBehavior = true,
+ afLockBehavior = customAfBehavior
+ )
+ val defaultAeBehavior = null
+ val defaultAwbBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN
+
+ val (ae, af, awb) =
+ lock3ABehavior.getLock3ABehaviors(
+ defaultAeBehavior,
+ defaultAwbBehavior = defaultAwbBehavior
+ )
+
+ assertThat(ae).isEqualTo(defaultAeBehavior) // Default used
+ assertThat(af).isEqualTo(customAfBehavior) // Custom used
+ assertThat(awb).isEqualTo(defaultAwbBehavior) // Default used
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
index 35fb7e0..f968130 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
@@ -48,11 +48,15 @@
import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
import androidx.camera.camera2.pipe.integration.compat.workaround.AeFpsRange
import androidx.camera.camera2.pipe.integration.compat.workaround.CapturePipelineTorchCorrection
+import androidx.camera.camera2.pipe.integration.compat.workaround.Lock3ABehaviorWhenCaptureImage
+import androidx.camera.camera2.pipe.integration.compat.workaround.Lock3ABehaviorWhenCaptureImage.Companion.doNotLockAe3ABehavior
+import androidx.camera.camera2.pipe.integration.compat.workaround.Lock3ABehaviorWhenCaptureImage.Companion.noCustomizedLock3ABehavior
import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpTemplateParamsOverride
import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseFlashModeTorchFor3aUpdate
import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseTorchAsFlash
import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
+import androidx.camera.camera2.pipe.integration.compat.workaround.UseTorchAsFlash
import androidx.camera.camera2.pipe.integration.compat.workaround.UseTorchAsFlashImpl
import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
@@ -153,6 +157,9 @@
var waitForAwbAtLock3AForCapture: Boolean = false
var cancelAfAtUnlock3AForCapture: Boolean = false
+ var aeLockBehavior: Lock3ABehavior? = null
+ var afLockBehavior: Lock3ABehavior? = null
+ var awbLockBehavior: Lock3ABehavior? = null
override suspend fun lock3A(
aeMode: AeMode?,
@@ -171,6 +178,9 @@
convergedTimeLimitNs: Long,
lockedTimeLimitNs: Long
): Deferred<Result3A> {
+ this.aeLockBehavior = aeLockBehavior
+ this.afLockBehavior = afLockBehavior
+ this.awbLockBehavior = awbLockBehavior
lock3ASemaphore.release()
return CompletableDeferred(Result3A(Result3A.Status.OK))
}
@@ -359,19 +369,7 @@
templateParamsOverride = NoOpTemplateParamsOverride,
)
- capturePipeline =
- CapturePipelineImpl(
- configAdapter = fakeCaptureConfigAdapter,
- cameraProperties = fakeCameraProperties,
- requestListener = comboRequestListener,
- threads = fakeUseCaseThreads,
- torchControl = torchControl,
- useCaseGraphConfig = fakeUseCaseGraphConfig,
- useCaseCameraState = fakeUseCaseCameraState,
- useTorchAsFlash = NotUseTorchAsFlash,
- sessionProcessorManager = null,
- flashControl = flashControl,
- )
+ capturePipeline = createCapturePipeline()
}
@After
@@ -475,19 +473,7 @@
private suspend fun TestScope.withTorchAsFlashQuirk_shouldOpenTorch(imageCaptureMode: Int) {
// Arrange.
- capturePipeline =
- CapturePipelineImpl(
- configAdapter = fakeCaptureConfigAdapter,
- cameraProperties = fakeCameraProperties,
- requestListener = comboRequestListener,
- threads = fakeUseCaseThreads,
- torchControl = torchControl,
- useCaseGraphConfig = fakeUseCaseGraphConfig,
- useCaseCameraState = fakeUseCaseCameraState,
- useTorchAsFlash = UseTorchAsFlashImpl,
- sessionProcessorManager = null,
- flashControl = flashControl,
- )
+ capturePipeline = createCapturePipeline(useTorchAsFlash = UseTorchAsFlashImpl)
val requestList = mutableListOf<Request>()
fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) }
@@ -602,22 +588,69 @@
@Test
fun miniLatency_flashRequired_withFlashTypeTorch_shouldLock3A(): Unit = runTest {
withFlashTypeTorch_shouldLock3A(
+ capturePipeline,
ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
- ImageCapture.FLASH_MODE_ON
+ ImageCapture.FLASH_MODE_ON,
+ expectedLock3ABehaviors =
+ Triple(
+ Lock3ABehavior.AFTER_CURRENT_SCAN,
+ Lock3ABehavior.AFTER_CURRENT_SCAN,
+ Lock3ABehavior.AFTER_CURRENT_SCAN
+ )
)
}
@Test
+ fun miniLatency_flashRequired_withFlashTypeTorch_doNotLockAe3ABehavior_shouldLock3A(): Unit =
+ runTest {
+ val capturePipeline =
+ createCapturePipeline(lock3ABehaviorWhenCaptureImage = doNotLockAe3ABehavior)
+ withFlashTypeTorch_shouldLock3A(
+ capturePipeline,
+ ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
+ ImageCapture.FLASH_MODE_ON,
+ expectedLock3ABehaviors =
+ Triple(
+ null,
+ Lock3ABehavior.AFTER_CURRENT_SCAN,
+ Lock3ABehavior.AFTER_CURRENT_SCAN
+ )
+ )
+ }
+
+ @Test
fun maxQuality_withFlashTypeTorch_shouldLock3A(): Unit = runTest {
withFlashTypeTorch_shouldLock3A(
+ capturePipeline,
ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
- ImageCapture.FLASH_MODE_OFF
+ ImageCapture.FLASH_MODE_OFF,
+ expectedLock3ABehaviors =
+ Triple(
+ Lock3ABehavior.AFTER_CURRENT_SCAN,
+ Lock3ABehavior.AFTER_CURRENT_SCAN,
+ Lock3ABehavior.AFTER_CURRENT_SCAN
+ )
+ )
+ }
+
+ @Test
+ fun maxQuality_withFlashTypeTorch_doNotLockAe3ABehavior_shouldLock3A(): Unit = runTest {
+ val capturePipeline =
+ createCapturePipeline(lock3ABehaviorWhenCaptureImage = doNotLockAe3ABehavior)
+ withFlashTypeTorch_shouldLock3A(
+ capturePipeline,
+ ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
+ ImageCapture.FLASH_MODE_OFF,
+ expectedLock3ABehaviors =
+ Triple(null, Lock3ABehavior.AFTER_CURRENT_SCAN, Lock3ABehavior.AFTER_CURRENT_SCAN)
)
}
private suspend fun TestScope.withFlashTypeTorch_shouldLock3A(
+ capturePipeline: CapturePipeline,
imageCaptureMode: Int,
- flashMode: Int
+ flashMode: Int,
+ expectedLock3ABehaviors: Triple<Lock3ABehavior?, Lock3ABehavior?, Lock3ABehavior?>,
) {
// Arrange.
val requestList = mutableListOf<Request>()
@@ -636,6 +669,10 @@
// Assert 1, should call lock3A, but not call unlock3A (before capturing is finished).
assertThat(fakeCameraGraphSession.lock3ASemaphore.tryAcquire(this)).isTrue()
assertThat(fakeCameraGraphSession.unlock3ASemaphore.tryAcquire(this)).isFalse()
+ // Ensure correct Lock3ABehaviors are set.
+ assertThat(fakeCameraGraphSession.aeLockBehavior).isEqualTo(expectedLock3ABehaviors.first)
+ assertThat(fakeCameraGraphSession.afLockBehavior).isEqualTo(expectedLock3ABehaviors.second)
+ assertThat(fakeCameraGraphSession.awbLockBehavior).isEqualTo(expectedLock3ABehaviors.third)
// Complete the capture request.
assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue()
@@ -1221,6 +1258,24 @@
assertThat(screenFlash.awaitClear(3000)).isTrue()
}
+ private fun createCapturePipeline(
+ useTorchAsFlash: UseTorchAsFlash = NotUseTorchAsFlash,
+ lock3ABehaviorWhenCaptureImage: Lock3ABehaviorWhenCaptureImage = noCustomizedLock3ABehavior
+ ) =
+ CapturePipelineImpl(
+ configAdapter = fakeCaptureConfigAdapter,
+ cameraProperties = fakeCameraProperties,
+ requestListener = comboRequestListener,
+ threads = fakeUseCaseThreads,
+ torchControl = torchControl,
+ useCaseGraphConfig = fakeUseCaseGraphConfig,
+ useCaseCameraState = fakeUseCaseCameraState,
+ useTorchAsFlash = useTorchAsFlash,
+ lock3ABehaviorWhenCaptureImage = lock3ABehaviorWhenCaptureImage,
+ sessionProcessorManager = null,
+ flashControl = flashControl,
+ )
+
// TODO(wenhungteng@): Porting overrideAeModeForStillCapture_quirkAbsent_notOverride,
// overrideAeModeForStillCapture_aePrecaptureStarted_override,
// overrideAeModeForStillCapture_aePrecaptureFinish_notOverride,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
index a62aef8..5698804 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
@@ -23,6 +23,7 @@
import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
import androidx.camera.camera2.pipe.integration.adapter.ZslControlNoOpImpl
+import androidx.camera.camera2.pipe.integration.compat.workaround.Lock3ABehaviorWhenCaptureImage.Companion.noCustomizedLock3ABehavior
import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpTemplateParamsOverride
import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseFlashModeTorchFor3aUpdate
import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseTorchAsFlash
@@ -435,6 +436,7 @@
useCaseGraphConfig = fakeUseCaseGraphConfig,
useCaseCameraState = fakeUseCaseCameraState,
useTorchAsFlash = NotUseTorchAsFlash,
+ lock3ABehaviorWhenCaptureImage = noCustomizedLock3ABehavior,
sessionProcessorManager = null,
flashControl =
FlashControl(
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCaptureSequenceProcessor.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCaptureSequenceProcessor.kt
index c6bb946..5010c90 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCaptureSequenceProcessor.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCaptureSequenceProcessor.kt
@@ -138,7 +138,7 @@
requestSequence?.invokeOnSequenceAborted()
}
- override fun close() {
+ override suspend fun shutdown() {
synchronized(lock) {
rejectRequests = true
check(eventChannel.trySend(Event(close = true)).isSuccess)
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CaptureSequenceProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CaptureSequenceProcessor.kt
index 5b4721e..77c8444 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CaptureSequenceProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CaptureSequenceProcessor.kt
@@ -71,5 +71,5 @@
* Signal that this [CaptureSequenceProcessor] is no longer in use. Active requests may continue
* to be processed, and [abortCaptures] and [stopRepeating] may still be invoked.
*/
- public fun close()
+ public suspend fun shutdown()
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
index 7549fe4..17fb37e 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
@@ -37,8 +37,6 @@
import androidx.camera.camera2.pipe.core.Debug
import androidx.camera.camera2.pipe.core.Log
import androidx.camera.camera2.pipe.core.Log.MonitoredLogMessages.REPEATING_REQUEST_STARTED_TIMEOUT
-import androidx.camera.camera2.pipe.core.Log.rethrowExceptionAfterLogging
-import androidx.camera.camera2.pipe.core.Threading.runBlockingWithTimeout
import androidx.camera.camera2.pipe.core.Threads
import androidx.camera.camera2.pipe.graph.StreamGraphImpl
import androidx.camera.camera2.pipe.media.AndroidImageWriter
@@ -47,6 +45,8 @@
import javax.inject.Inject
import kotlin.reflect.KClass
import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.withTimeout
internal interface Camera2CaptureSequenceProcessorFactory {
fun create(
@@ -181,14 +181,13 @@
// Finally, write required parameters to the request builder. This will override any
// value that has ben previously set.
//
- // TODO(sushilnath@): Implement one of the two options. (1) Apply the 3A parameters
- // from internal 3A state machine at last and provide a flag in the Request object
- // to
- // specify when the clients want to explicitly override some of the 3A parameters
- // directly. Add code to handle the flag. (2) Let clients override the 3A parameters
- // freely and when that happens intercept those parameters from the request and keep
- // the
- // internal 3A state machine in sync.
+ // TODO(sushilnath@): Implement one of the two options
+ // (1) Apply the 3A parameters from internal 3A state machine at last and provide
+ // a flag in the Request object to specify when the clients want to explicitly
+ // override some of the 3A parameters directly. Add code to handle the flag.
+ // (2) Let clients override the 3A parameters freely and when that happens
+ // intercept those parameters from the request and keep the internal 3A state
+ // machine in sync.
requestBuilder.writeParameters(requiredParameters)
}
val requestNumber = nextRequestNumber()
@@ -284,9 +283,7 @@
override fun submit(captureSequence: Camera2CaptureSequence): Int? =
synchronized(lock) {
if (closed) {
- Log.warn {
- "Capture sequence processor closed. $captureSequence won't be submitted"
- }
+ Log.warn { "$this closed. $captureSequence won't be submitted" }
return null
}
val captureCallback = captureSequence as CameraCaptureSession.CaptureCallback
@@ -327,48 +324,52 @@
session.stopRepeating()
}
- override fun close() =
+ override suspend fun shutdown() {
+ val captureSequence: Camera2CaptureSequence?
synchronized(lock) {
if (closed) {
- return@synchronized
+ return
}
- // Close should not shut down
- Debug.trace("$this#close") {
- if (shouldWaitForRepeatingRequest) {
- lastSingleRepeatingRequestSequence?.let {
- Log.debug { "Waiting for the last repeating request sequence $it" }
- // On certain devices, the submitted repeating request sequence may not give
- // us
- // onCaptureStarted() or onCaptureSequenceAborted() [1]. Hence we wrap the
- // wait
- // under a timeout to prevent us from waiting forever.
- //
- // [1] b/307588161 - [ANR] at
- //
- // androidx.camera.camera2.pipe.compat.Camera2CaptureSequenceProcessor.close
- rethrowExceptionAfterLogging(
- "$this#close: $REPEATING_REQUEST_STARTED_TIMEOUT" +
- ", lastSingleRepeatingRequestSequence = $it"
- ) {
- runBlockingWithTimeout(
- threads.backgroundDispatcher,
- WAIT_FOR_REPEATING_TIMEOUT_MS
- ) {
- it.awaitStarted()
- }
- }
- }
- }
+ closed = true
+ captureSequence = lastSingleRepeatingRequestSequence
+ }
+
+ if (shouldWaitForRepeatingRequest && captureSequence != null) {
+ awaitRepeatingRequestStarted(captureSequence)
+ }
+
+ // Shutdown is responsible for releasing resources that are no longer in use.
+ Debug.trace("$this#close") {
+ synchronized(lock) {
imageWriter?.close()
session.inputSurface?.release()
- closed = true
}
}
+ }
override fun toString(): String {
return "Camera2CaptureSequenceProcessor-$debugId"
}
+ private suspend fun awaitRepeatingRequestStarted(captureSequence: Camera2CaptureSequence) {
+ Log.debug { "Waiting for the last repeating request sequence: $captureSequence" }
+ // On certain devices, the submitted repeating request sequence may not give
+ // us onCaptureStarted() or onCaptureSequenceAborted() [1]. Hence we wrap
+ // the wait under a timeout to prevent us from waiting forever.
+ //
+ // [1] b/307588161 - [ANR] at
+ // androidx.camera.camera2.pipe.compat.Camera2CaptureSequenceProcessor.close
+ try {
+ withTimeout(WAIT_FOR_REPEATING_TIMEOUT_MS) { captureSequence.awaitStarted() }
+ } catch (e: TimeoutCancellationException) {
+ Log.error {
+ "$this#close: $REPEATING_REQUEST_STARTED_TIMEOUT" +
+ ", lastSingleRepeatingRequestSequence = $captureSequence"
+ }
+ throw e
+ }
+ }
+
/**
* The [ImageWriterWrapper] is created once per capture session when the capture session is
* created, assuming it's a reprocessing session.
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
index 0379996..15425cb 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
@@ -40,6 +40,7 @@
import androidx.camera.camera2.pipe.graph.GraphRequestProcessor
import kotlin.reflect.KClass
import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.runBlocking
public class ExternalCameraController(
private val graphId: CameraGraphId,
@@ -78,7 +79,9 @@
}
override fun close() {
- graphProcessor.close()
+ // TODO: ExternalRequestProcessor will be deprecated. This is a temporary patch to allow
+ // graphProcessor to have a suspending shutdown function.
+ runBlocking { graphProcessor.shutdown() }
}
override fun updateSurfaceMap(surfaceMap: Map<StreamId, Surface>) {
@@ -189,7 +192,7 @@
processor.stopRepeating()
}
- override fun close() {
+ override suspend fun shutdown() {
if (closed.compareAndSet(expect = false, update = true)) {
processor.close()
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
index b805289..a01f102 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
@@ -44,7 +44,7 @@
* @param label A name of the code section to appear in the trace.
* @param block A block of code which is being traced.
*/
- public inline fun <T> trace(label: String, block: () -> T): T {
+ public inline fun <T> trace(label: String, crossinline block: () -> T): T {
try {
traceStart { label }
return block()
@@ -54,7 +54,7 @@
}
/** Wrap the specified [block] in a trace and timing calls. */
- internal inline fun <T> instrument(label: String, block: () -> T): T {
+ internal inline fun <T> instrument(label: String, crossinline block: () -> T): T {
val start = systemTimeSource.now()
try {
traceStart { label }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImpl.kt
index eaef6a4..c552afc 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImpl.kt
@@ -67,7 +67,7 @@
override fun startRepeating(request: Request) {
check(!token.released) { "Cannot call startRepeating on $this after close." }
- graphProcessor.startRepeating(request)
+ graphProcessor.repeatingRequest = request
}
override fun abort() {
@@ -77,8 +77,7 @@
override fun stopRepeating() {
check(!token.released) { "Cannot call stopRepeating on $this after close." }
- graphProcessor.stopRepeating()
- controller3A.onStopRepeating()
+ graphProcessor.repeatingRequest = null
}
override fun close() {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CaptureLimiter.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CaptureLimiter.kt
new file mode 100644
index 0000000..492d7f9
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CaptureLimiter.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.graph
+
+import androidx.camera.camera2.pipe.FrameInfo
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestMetadata
+import androidx.camera.camera2.pipe.core.Log
+import kotlinx.atomicfu.atomic
+import kotlinx.atomicfu.update
+import kotlinx.atomicfu.updateAndGet
+
+/**
+ * On some devices, we need to wait for 10 frames to complete before we can guarantee the success of
+ * single capture requests. This is a quirk identified as part of b/287020251 and reported in
+ * b/289284907.
+ *
+ * During initialization, setting the graphLoop will disableCaptureProcessing until after the
+ * required number of frames have been completed.
+ */
+internal class CaptureLimiter(private val requestsUntilActive: Long) :
+ Request.Listener, GraphLoop.Listener {
+ init {
+ require(requestsUntilActive > 0)
+ }
+
+ private val frameCount = atomic(0L)
+ private var _graphLoop: GraphLoop? = null
+ var graphLoop: GraphLoop
+ get() = _graphLoop!!
+ set(value) {
+ check(_graphLoop == null) { "GraphLoop has already been set!" }
+ _graphLoop = value
+ value.captureProcessingEnabled = false
+ Log.warn {
+ "Capture processing has been disabled for $value until $requestsUntilActive " +
+ "frames have been completed."
+ }
+ }
+
+ override fun onComplete(
+ requestMetadata: RequestMetadata,
+ frameNumber: FrameNumber,
+ result: FrameInfo
+ ) {
+ val count = frameCount.updateAndGet { if (it == -1L) -1 else it + 1 }
+ if (count == requestsUntilActive) {
+ Log.warn { "Capture processing is now enabled for $_graphLoop after $count frames." }
+ graphLoop.captureProcessingEnabled = true
+ }
+ }
+
+ override fun onStopRepeating() {
+ // Ignored
+ }
+
+ override fun onGraphStopped() {
+ // If the cameraGraph is stopped, reset the counter
+ frameCount.update { if (it == -1L) -1 else 0 }
+ graphLoop.captureProcessingEnabled = false
+ Log.warn {
+ "Capture processing has been disabled for $graphLoop until $requestsUntilActive " +
+ "frames have been completed."
+ }
+ }
+
+ override fun onGraphShutdown() {
+ frameCount.value = -1
+ graphLoop.captureProcessingEnabled = false
+ }
+}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt
index 841683ec..177ce94 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt
@@ -186,7 +186,7 @@
): Deferred<Result3A> {
// If the GraphProcessor does not have a repeating request we should update the current
// parameters, but should not invalidate or trigger set a new listener.
- if (!graphProcessor.hasRepeatingRequest()) {
+ if (graphProcessor.repeatingRequest == null) {
graphState3A.update(
aeMode,
afMode,
@@ -222,7 +222,7 @@
return result
}
- suspend fun submit3A(
+ fun submit3A(
aeMode: AeMode? = null,
afMode: AfMode? = null,
awbMode: AwbMode? = null,
@@ -231,9 +231,10 @@
awbRegions: List<MeteringRectangle>? = null
): Deferred<Result3A> {
// If the GraphProcessor does not have a repeating request, we should fail immediately.
- if (!graphProcessor.hasRepeatingRequest()) {
+ if (graphProcessor.repeatingRequest == null) {
return deferredResult3ASubmitFailed
}
+
// Add the listener to a global pool of 3A listeners to monitor the state change to the
// desired one.
val listener = createListenerFor3AParams(aeMode, afMode, awbMode)
@@ -247,7 +248,7 @@
afRegions?.let { extra3AParams.put(CaptureRequest.CONTROL_AF_REGIONS, it.toTypedArray()) }
awbRegions?.let { extra3AParams.put(CaptureRequest.CONTROL_AWB_REGIONS, it.toTypedArray()) }
- if (!graphProcessor.trySubmit(extra3AParams)) {
+ if (!graphProcessor.submit(extra3AParams)) {
graphListener3A.removeListener(listener)
return deferredResult3ASubmitFailed
}
@@ -308,7 +309,7 @@
// If the GraphProcessor does not have a repeating request we should update the current
// parameters, but should not invalidate or trigger set a new listener.
- if (!graphProcessor.hasRepeatingRequest()) {
+ if (graphProcessor.repeatingRequest == null) {
return deferredResult3ASubmitFailed
}
@@ -316,7 +317,7 @@
// a single request with TRIGGER = TRIGGER_CANCEL so that af can start a fresh scan.
if (afLockBehaviorSanitized.shouldUnlockAf()) {
debug { "lock3A - sending a request to unlock af first." }
- if (!graphProcessor.trySubmit(parameterForAfTriggerCancel)) {
+ if (!graphProcessor.submit(parameterForAfTriggerCancel)) {
return deferredResult3ASubmitFailed
}
}
@@ -394,7 +395,7 @@
* There are two requests involved in this operation, (a) a single request with af trigger =
* cancel, to unlock af, and then (a) a repeating request to unlock ae, awb.
*/
- suspend fun unlock3A(
+ fun unlock3A(
ae: Boolean? = null,
af: Boolean? = null,
awb: Boolean? = null,
@@ -410,17 +411,14 @@
return CompletableDeferred(Result3A(Status.OK, /* frameMetadata= */ null))
}
// If the GraphProcessor does not have a repeating request, we should fail immediately.
- if (!graphProcessor.hasRepeatingRequest()) {
+ if (graphProcessor.repeatingRequest == null) {
return deferredResult3ASubmitFailed
}
// If we explicitly need to unlock af first before proceeding to lock it, we need to send
// a single request with TRIGGER = TRIGGER_CANCEL so that af can start a fresh scan.
if (afSanitized == true) {
debug { "unlock3A - sending a request to unlock af first." }
- if (!graphProcessor.trySubmit(parameterForAfTriggerCancel)) {
- debug { "unlock3A - request to unlock af failed, returning early." }
- return deferredResult3ASubmitFailed
- }
+ graphProcessor.submit(parameterForAfTriggerCancel)
}
// As needed unlock ae, awb and wait for ae, af and awb to converge.
@@ -463,7 +461,7 @@
* which the locks were applied or the frame number at which the method returned early because
* either frame limit or time limit was reached.
*/
- suspend fun lock3AForCapture(
+ fun lock3AForCapture(
lockedCondition: ((FrameMetadata) -> Boolean)? = null,
frameLimit: Int = DEFAULT_FRAME_LIMIT,
timeLimitNs: Long = DEFAULT_TIME_LIMIT_NS,
@@ -496,7 +494,7 @@
* which the locks were applied or the frame number at which the method returned early because
* either frame limit or time limit was reached.
*/
- suspend fun lock3AForCapture(
+ fun lock3AForCapture(
triggerAf: Boolean = true,
waitForAwb: Boolean = false,
frameLimit: Int = DEFAULT_FRAME_LIMIT,
@@ -538,14 +536,14 @@
* which the locks were applied or the frame number at which the method returned early because
* either frame limit or time limit was reached.
*/
- private suspend fun lock3AForCapture(
+ private fun lock3AForCapture(
triggerCondition: Map<CaptureRequest.Key<*>, Any>? = null,
lockedCondition: ((FrameMetadata) -> Boolean)? = null,
frameLimit: Int = DEFAULT_FRAME_LIMIT,
timeLimitNs: Long = DEFAULT_TIME_LIMIT_NS,
): Deferred<Result3A> {
// If the GraphProcessor does not have a repeating request, we should fail immediately.
- if (!graphProcessor.hasRepeatingRequest()) {
+ if (graphProcessor.repeatingRequest == null) {
return deferredResult3ASubmitFailed
}
@@ -569,24 +567,19 @@
)
graphListener3A.addListener(listener)
-
debug { "lock3AForCapture - sending a request to trigger ae precapture metering and af." }
- if (!graphProcessor.trySubmit(finalTriggerCondition)) {
- debug {
- "lock3AForCapture - request to trigger ae precapture metering and af failed, " +
- "returning early."
- }
+
+ if (!graphProcessor.submit(finalTriggerCondition)) {
graphListener3A.removeListener(listener)
return deferredResult3ASubmitFailed
}
-
graphProcessor.invalidate()
return listener.result
}
- suspend fun unlock3APostCapture(cancelAf: Boolean = true): Deferred<Result3A> {
+ fun unlock3APostCapture(cancelAf: Boolean = true): Deferred<Result3A> {
// If the GraphProcessor does not have a repeating request, we should fail immediately.
- if (!graphProcessor.hasRepeatingRequest()) {
+ if (graphProcessor.repeatingRequest == null) {
return deferredResult3ASubmitFailed
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -601,22 +594,15 @@
* REF :
* https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
*/
- private suspend fun unlock3APostCaptureAndroidLAndBelow(
- cancelAf: Boolean = true
- ): Deferred<Result3A> {
+ private fun unlock3APostCaptureAndroidLAndBelow(cancelAf: Boolean = true): Deferred<Result3A> {
debug { "unlock3AForCapture - sending a request to cancel af and turn on ae." }
- if (
- !graphProcessor.trySubmit(
- if (cancelAf) {
- unlock3APostCaptureLockAeAndCancelAfParams
- } else {
- unlock3APostCaptureLockAeParams
- }
- )
- ) {
- debug { "unlock3AForCapture - request to cancel af and lock ae as failed." }
- return deferredResult3ASubmitFailed
- }
+ val cancelParams =
+ if (cancelAf) {
+ unlock3APostCaptureLockAeAndCancelAfParams
+ } else {
+ unlock3APostCaptureLockAeParams
+ }
+ if (!graphProcessor.submit(cancelParams)) return deferredResult3ASubmitFailed
// Listener to monitor when we receive the capture result corresponding to the request
// below.
@@ -624,8 +610,7 @@
graphListener3A.addListener(listener)
debug { "unlock3AForCapture - sending a request to turn off ae." }
- if (!graphProcessor.trySubmit(unlock3APostCaptureUnlockAeParams)) {
- debug { "unlock3AForCapture - request to unlock ae failed." }
+ if (!graphProcessor.submit(unlock3APostCaptureUnlockAeParams)) {
graphListener3A.removeListener(listener)
return deferredResult3ASubmitFailed
}
@@ -639,16 +624,10 @@
* https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
*/
@RequiresApi(23)
- private suspend fun unlock3APostCaptureAndroidMAndAbove(
- cancelAf: Boolean = true
- ): Deferred<Result3A> {
+ private fun unlock3APostCaptureAndroidMAndAbove(cancelAf: Boolean = true): Deferred<Result3A> {
debug { "unlock3APostCapture - sending a request to reset af and ae precapture metering." }
val cancelParams = if (cancelAf) aePrecaptureAndAfCancelParams else aePrecaptureCancelParams
- if (!graphProcessor.trySubmit(cancelParams)) {
- debug {
- "unlock3APostCapture - request to reset af and ae precapture metering failed, " +
- "returning early."
- }
+ if (!graphProcessor.submit(cancelParams)) {
return deferredResult3ASubmitFailed
}
@@ -677,11 +656,7 @@
return update3A(aeMode = desiredAeMode, flashMode = flashMode)
}
- internal fun onStopRepeating() {
- graphListener3A.onStopRepeating()
- }
-
- private suspend fun lock3ANow(
+ private fun lock3ANow(
aeLockBehavior: Lock3ABehavior?,
afLockBehavior: Lock3ABehavior?,
awbLockBehavior: Lock3ABehavior?,
@@ -724,7 +699,9 @@
}
debug { "lock3A - submitting a request to lock af." }
- val submitSuccess = graphProcessor.trySubmit(parameterForAfTriggerStart)
+ if (!graphProcessor.submit(parameterForAfTriggerStart)) {
+ return deferredResult3ASubmitFailed
+ }
lastAeMode?.let {
graphState3A.update(aeMode = it)
@@ -733,14 +710,6 @@
// w.r.t. building the parameter snapshot
graphProcessor.invalidate()
}
-
- if (!submitSuccess) {
- // TODO(sushilnath@): Change the error code to a more specific code so it's clear
- // that one of the request in sequence of requests failed and the caller should
- // unlock 3A to bring the 3A system to an initial state and then try again if they
- // want to. The other option is to reset or restore the 3A state here.
- return deferredResult3ASubmitFailed
- }
return resultForLocked!!
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphLoop.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphLoop.kt
index 7811490..80ce6b0f 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphLoop.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphLoop.kt
@@ -17,6 +17,7 @@
package androidx.camera.camera2.pipe.graph
import androidx.annotation.GuardedBy
+import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.Request
import androidx.camera.camera2.pipe.core.Debug
import androidx.camera.camera2.pipe.core.Log
@@ -24,10 +25,12 @@
import androidx.camera.camera2.pipe.core.ProcessingQueue.Companion.processIn
import androidx.camera.camera2.pipe.putAllMetadata
import java.io.Closeable
+import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
/**
* GraphLoop is a thread-safe class that handles incoming state changes and requests and executes
@@ -36,12 +39,23 @@
* cleanup of pending requests during shutdown.
*/
internal class GraphLoop(
- private val defaultParameters: Map<Any, Any?>,
- private val requiredParameters: Map<Any, Any?>,
- private val listeners: List<Request.Listener>,
+ private val cameraGraphId: CameraGraphId,
+ private val defaultParameters: Map<*, Any?>,
+ private val requiredParameters: Map<*, Any?>,
+ private val graphListeners: List<Request.Listener>,
private val graphState3A: GraphState3A?,
+ private val listeners: List<GraphLoop.Listener>,
+ private val shutdownScope: CoroutineScope,
dispatcher: CoroutineDispatcher
) : Closeable {
+ internal interface Listener {
+ fun onStopRepeating()
+
+ fun onGraphStopped()
+
+ fun onGraphShutdown()
+ }
+
private val lock = Any()
private val graphProcessorScope =
CoroutineScope(dispatcher.plus(CoroutineName("CXCP-GraphLoop")))
@@ -57,71 +71,112 @@
var requestProcessor: GraphRequestProcessor?
get() = synchronized(lock) { _requestProcessor }
- set(value) =
+ set(value) {
synchronized(lock) {
- check(!closed)
val previous = _requestProcessor
_requestProcessor = value
- // Ignore duplicate calls to set with the same value.
- if (previous !== value) {
- if (previous != null) {
- // Closing the request processor can (sometimes) block the calling thread.
- // Make
- // sure this is invoked in the background.
- processingQueue.tryEmit(CloseRequestProcessor(previous))
- }
-
+ if (closed) {
+ _requestProcessor = null
if (value != null) {
- val repeatingRequest = _repeatingRequest
- if (repeatingRequest == null) {
- // This handles the case where a single request has been issued before
- // the GraphRequestProcessor was configured when there is not repeating
- // request.
- processingQueue.tryEmit(Invalidate)
- } else {
- // If there is an active repeating request, make sure the request is
- // issued to the new request processor.
- processingQueue.tryEmit(StartRepeating(repeatingRequest))
- }
+ shutdownScope.launch { value.shutdown() }
+ }
+ return
+ }
+
+ // Ignore duplicate calls to set with the same value.
+ if (previous === value) {
+ return@synchronized
+ }
+
+ if (previous != null) {
+ // Closing the request processor can (sometimes) block the calling thread.
+ // Make sure this is invoked in the background.
+ processingQueue.tryEmit(CloseRequestProcessor(previous))
+ }
+
+ if (value != null) {
+ val repeatingRequest = _repeatingRequest
+ if (repeatingRequest == null) {
+ // This handles the case where a single request has been issued before
+ // the GraphRequestProcessor was configured when there is not repeating
+ // request. Invalidate will cause the commandLoop to re-evaluate, which
+ // may succeed now that a valid request processor has been configured.
+ processingQueue.tryEmit(Invalidate)
+ } else {
+ // If there is an active repeating request, make sure the request is
+ // issued to the new request processor. This serves the same purpose as
+ // Invalidate which re-triggers the commandLoop.
+ processingQueue.tryEmit(StartRepeating(repeatingRequest))
}
}
}
+ if (value == null) {
+ for (i in listeners.indices) {
+ listeners[i].onGraphStopped()
+ }
+ }
+ }
+
var repeatingRequest: Request?
get() = synchronized(lock) { _repeatingRequest }
- set(value) =
+ set(value) {
synchronized(lock) {
val previous = _repeatingRequest
_repeatingRequest = value
- // Ignore duplicate calls to set with the same value.
- if (previous !== value) {
- if (value != null) {
- processingQueue.tryEmit(StartRepeating(value))
- } else {
- // If the repeating request is set to null, stop repeating (using the
- // current request processor instance), this is allowed because stop and
- // abort can be called on a requestProcessor that has, or is in the
- // process, of being released.
- processingQueue.tryEmit(StopRepeating(_requestProcessor))
- }
+ // Ignore duplicate calls to set null, this avoids multiple stopRepeating calls from
+ // being invoked.
+ if (previous == null && value == null) {
+ return@synchronized
+ }
+
+ if (value != null) {
+ processingQueue.tryEmit(StartRepeating(value))
+ } else {
+ // If the repeating request is set to null, stop repeating (using the current
+ // request processor instance). This is allowed because stop and abort can be
+ // called on a requestProcessor that has, or is in the process, of being
+ // released.
+ processingQueue.tryEmit(StopRepeating(_requestProcessor))
}
}
- fun submit(requests: List<Request>) {
+ if (value == null) {
+ for (i in listeners.indices) {
+ listeners[i].onStopRepeating()
+ }
+ }
+ }
+
+ private val _captureProcessingEnabled = atomic(true)
+ var captureProcessingEnabled: Boolean
+ get() = _captureProcessingEnabled.value
+ set(value) {
+ _captureProcessingEnabled.value = value
+ if (value) {
+ invalidate()
+ }
+ }
+
+ fun submit(request: Request): Boolean = submit(listOf(request))
+
+ fun submit(requests: List<Request>): Boolean {
if (!processingQueue.tryEmit(SubmitCapture(requests))) {
abortRequests(requests)
+ return false
}
+ return true
}
- fun submit(parameters: Map<Any, Any?>) {
+ fun submit(parameters: Map<*, Any?>): Boolean {
synchronized(lock) {
val currentRepeatingRequest = _repeatingRequest
check(currentRepeatingRequest != null) {
"Cannot submit parameters without an active repeating request!"
}
- processingQueue.tryEmit(SubmitParameters(currentRepeatingRequest, parameters))
+ return processingQueue.tryEmit(SubmitParameters(currentRepeatingRequest, parameters))
}
}
@@ -155,6 +210,10 @@
// close the request processor before cancelling the scope.
processingQueue.tryEmit(Shutdown(previousRequestProcessor))
}
+
+ for (i in listeners.indices) {
+ listeners[i].onGraphShutdown()
+ }
}
/**
@@ -165,12 +224,12 @@
// Internal listeners
for (rIdx in requests.indices) {
val request = requests[rIdx]
- for (listenerIdx in listeners.indices) {
- listeners[listenerIdx].onAborted(request)
+ for (listenerIdx in graphListeners.indices) {
+ graphListeners[listenerIdx].onAborted(request)
}
}
- // Request listeners
+ // Request-specific listeners
for (rIdx in requests.indices) {
val request = requests[rIdx]
for (listenerIdx in request.listeners.indices) {
@@ -191,7 +250,7 @@
private var lastRepeatingRequest: Request? = null
- private fun commandLoop(commands: MutableList<GraphCommand>) {
+ private suspend fun commandLoop(commands: MutableList<GraphCommand>) {
// Command Loop Design:
//
// 1. Iterate through commands, newest first.
@@ -257,19 +316,22 @@
// If the request processor is not null, shut it down. Consider making this a
// suspending call instead of just blocking to allow suspend-with-timeout.
- command.requestProcessor?.close()
+ command.requestProcessor?.shutdown()
- // Cancel the scope.
+ // Cancel the scope. This will trigger the onUnprocessedItems callback after after
+ // this hits the next suspension point.
graphProcessorScope.cancel()
}
is CloseRequestProcessor -> {
commands.removeAt(idx)
- // TODO: Consider making this a suspending call. This would allow things like
- // "await repeating request" to suspend-with-timeout instead of blocking.
- command.requestProcessor.close()
+ command.requestProcessor.shutdown()
}
is AbortCaptures -> {
commands.removeAt(idx)
+
+ // Attempt to abort captures in the approximate order they were submitted:
+ // 1. Abort captures submitted to the camera
+ // 2. Invoke abort on captures that have not yet been submitted to the camera.
if (command.requestProcessor != null) {
command.requestProcessor.abortCaptures()
}
@@ -315,6 +377,13 @@
commands.removeUpTo(idx) { it is StartRepeating }
}
is SubmitCapture -> {
+ if (!_captureProcessingEnabled.value) {
+ Log.warn {
+ "Skipping SubmitCapture because capture processing is paused: " +
+ "${command.requests}"
+ }
+ return
+ }
val success =
requestProcessor?.buildAndSubmit(
isRepeating = false,
@@ -330,6 +399,14 @@
}
}
is SubmitParameters -> {
+ if (!_captureProcessingEnabled.value) {
+ Log.warn {
+ "Skipping SubmitParameters because capture processing is paused: " +
+ "${command.parameters}"
+ }
+ return
+ }
+
val success =
requestProcessor?.buildAndSubmit(
isRepeating = false,
@@ -368,7 +445,7 @@
private fun GraphRequestProcessor.buildAndSubmit(
isRepeating: Boolean,
requests: List<Request>,
- parameters: Map<Any, Any?> = emptyMap()
+ parameters: Map<*, Any?> = emptyMap<Any, Any?>()
): Boolean {
val graphRequiredParameters = buildMap {
// Build the required parameter map:
@@ -384,10 +461,12 @@
requests = requests,
defaultParameters = defaultParameters,
requiredParameters = graphRequiredParameters,
- listeners = listeners
+ listeners = graphListeners
)
}
+ override fun toString(): String = "GraphLoop($cameraGraphId)"
+
private sealed class GraphCommand
private object Invalidate : GraphCommand()
@@ -405,6 +484,6 @@
private class SubmitCapture(val requests: List<Request>) : GraphCommand()
- private class SubmitParameters(val request: Request, val parameters: Map<Any, Any?>) :
+ private class SubmitParameters(val request: Request, val parameters: Map<*, Any?>) :
GraphCommand()
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
index 42035e2..1233c6b 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
@@ -15,8 +15,6 @@
*/
package androidx.camera.camera2.pipe.graph
-import android.os.Build
-import androidx.annotation.GuardedBy
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.CaptureSequenceProcessor
@@ -34,25 +32,14 @@
import androidx.camera.camera2.pipe.compat.CameraPipeKeys
import androidx.camera.camera2.pipe.config.CameraGraphScope
import androidx.camera.camera2.pipe.config.ForCameraGraph
-import androidx.camera.camera2.pipe.core.CoroutineMutex
-import androidx.camera.camera2.pipe.core.Debug
import androidx.camera.camera2.pipe.core.Log.debug
import androidx.camera.camera2.pipe.core.Log.info
-import androidx.camera.camera2.pipe.core.Log.warn
import androidx.camera.camera2.pipe.core.Threads
-import androidx.camera.camera2.pipe.core.withLockLaunch
-import androidx.camera.camera2.pipe.formatForLogs
-import androidx.camera.camera2.pipe.putAllMetadata
import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
import javax.inject.Inject
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
/**
* The [GraphProcessor] is responsible for queuing and then submitting them to a
@@ -62,18 +49,20 @@
internal interface GraphProcessor {
val graphState: StateFlow<GraphState>
- fun submit(request: Request)
+ /**
+ * The currently configured repeating request. Setting this value to null will attempt to call
+ * stopRepeating on the Camera.
+ */
+ var repeatingRequest: Request?
- fun submit(requests: List<Request>)
+ fun submit(request: Request): Boolean
+
+ fun submit(requests: List<Request>): Boolean
/**
- * This tries to submit a list of parameters — essentially a list of request settings usually
- * from 3A methods. It does this by setting the given parameters onto the current repeating
- * request on a best-effort basis.
- *
- * If the CameraGraph hasn't been started yet, but we do have a pending repeating request
- * queued, the method will suspend until we have a submitted repeating request and only then
- * submits the parameters.
+ * This tries to submit a list of parameters based on the current repeating request. If the
+ * CameraGraph hasn't been started but a valid repeating request has already been set this
+ * method will enqueue the submission based on the repeating request.
*
* This behavior is required if users call 3A methods immediately after start. For example:
* ```
@@ -88,21 +77,9 @@
* implementation handles this on a best-effort basis for the developer. Please read b/263211462
* for more context.
*
- * However, if the CameraGraph does NOT have a current repeating request or any repeating
- * requests queued up, the method will return false.
+ * This method will throw a checked exception if no repeating request has been configured.
*/
- suspend fun trySubmit(parameters: Map<*, Any?>): Boolean
-
- fun startRepeating(request: Request)
-
- fun stopRepeating()
-
- /**
- * Checks whether we have a repeating request in progress. Returns true when we have a repeating
- * request already submitted or is being submitted. This is used to check whether we can try to
- * submit parameters (used by 3A methods).
- */
- fun hasRepeatingRequest(): Boolean
+ fun submit(parameters: Map<*, Any?>): Boolean
/**
* Indicates that internal parameters may have changed, and that the repeating request should be
@@ -128,29 +105,56 @@
internal class GraphProcessorImpl
@Inject
constructor(
- private val threads: Threads,
+ threads: Threads,
private val cameraGraphId: CameraGraphId,
private val cameraGraphConfig: CameraGraph.Config,
- private val graphState3A: GraphState3A,
- @ForCameraGraph private val graphScope: CoroutineScope,
- @ForCameraGraph private val graphListeners: List<@JvmSuppressWildcards Request.Listener>
+ graphState3A: GraphState3A,
+ graphListener3A: Listener3A,
+ @ForCameraGraph graphListeners: List<@JvmSuppressWildcards Request.Listener>
) : GraphProcessor, GraphListener {
- private val lock = Any()
- private val tryStartRepeatingExecutionLock = Any()
- private val coroutineMutex = CoroutineMutex()
- @GuardedBy("lock") private val submitQueue: MutableList<List<Request>> = ArrayList()
- @GuardedBy("lock") private val repeatingQueue: MutableList<Request> = ArrayList()
- @GuardedBy("lock") private var currentRepeatingRequest: Request? = null
- @GuardedBy("lock") private var _requestProcessor: GraphRequestProcessor? = null
- @GuardedBy("lock") private var submitting = false
- @GuardedBy("lock") private var dirty = false
- @GuardedBy("lock") private var closed = false
- @GuardedBy("lock") private var pendingParameters: Map<*, Any?>? = null
- @GuardedBy("lock") private var pendingParametersDeferred: CompletableDeferred<Boolean>? = null
+ private val graphLoop: GraphLoop
+
+ init {
+ val defaultParameters = cameraGraphConfig.defaultParameters
+ val requiredParameters = cameraGraphConfig.requiredParameters
+ val ignore3AState =
+ (defaultParameters[CameraPipeKeys.ignore3ARequiredParameters] == true) ||
+ (requiredParameters[CameraPipeKeys.ignore3ARequiredParameters] == true)
+
+ if (ignore3AState) {
+ info {
+ "${CameraPipeKeys.ignore3ARequiredParameters} is set to true, " +
+ "ignoring GraphState3A parameters."
+ }
+ }
+
+ val captureLimiter =
+ if (Camera2Quirks.shouldWaitForRepeatingBeforeCapture()) {
+ CaptureLimiter(10)
+ } else {
+ null
+ }
+
+ graphLoop =
+ GraphLoop(
+ cameraGraphId = cameraGraphId,
+ defaultParameters = defaultParameters,
+ requiredParameters = requiredParameters,
+ graphListeners = graphListeners + listOfNotNull(captureLimiter),
+ graphState3A = if (ignore3AState) null else graphState3A,
+ listeners = listOfNotNull(graphListener3A, captureLimiter),
+ shutdownScope = threads.globalScope,
+ dispatcher = threads.lightweightDispatcher
+ )
+
+ captureLimiter?.graphLoop = graphLoop
+ }
+
// On some devices, we need to wait for 10 frames to complete before we can guarantee the
// success of single capture requests. This is a quirk identified as part of b/287020251 and
// reported in b/289284907.
private var repeatingRequestsCompleted = CountDownLatch(10)
+
// Graph listener added to repeating requests in order to handle the aforementioned quirk.
private val graphProcessorRepeatingListeners =
if (!Camera2Quirks.shouldWaitForRepeatingBeforeCapture()) {
@@ -171,6 +175,12 @@
override val graphState: StateFlow<GraphState>
get() = _graphState
+ override var repeatingRequest: Request?
+ get() = graphLoop.repeatingRequest
+ set(value) {
+ graphLoop.repeatingRequest = value
+ }
+
override fun onGraphStarting() {
debug { "$this onGraphStarting" }
_graphState.value = GraphStateStarting
@@ -179,22 +189,7 @@
override fun onGraphStarted(requestProcessor: GraphRequestProcessor) {
debug { "$this onGraphStarted" }
_graphState.value = GraphStateStarted
- var old: GraphRequestProcessor? = null
- synchronized(lock) {
- if (closed) {
- requestProcessor.close()
- return
- }
- if (_requestProcessor != null && _requestProcessor !== requestProcessor) {
- old = _requestProcessor
- }
- _requestProcessor = requestProcessor
- }
- val processorToClose = old
- if (processorToClose != null) {
- synchronized(processorToClose) { processorToClose.close() }
- }
- resubmit()
+ graphLoop.requestProcessor = requestProcessor
}
override fun onGraphStopping() {
@@ -205,39 +200,12 @@
override fun onGraphStopped(requestProcessor: GraphRequestProcessor?) {
debug { "$this onGraphStopped" }
_graphState.value = GraphStateStopped
- if (requestProcessor == null) return
- var old: GraphRequestProcessor? = null
- synchronized(lock) {
- if (closed) {
- return
- }
- if (requestProcessor === _requestProcessor) {
- old = _requestProcessor
- _requestProcessor = null
- } else {
- warn {
- "Refusing to detach $requestProcessor. " +
- "It is different from $_requestProcessor"
- }
- }
- }
- val processorToClose = old
- if (processorToClose != null) {
- synchronized(processorToClose) { processorToClose.close() }
- }
+ graphLoop.requestProcessor = null
}
override fun onGraphModified(requestProcessor: GraphRequestProcessor) {
debug { "$this onGraphModified" }
- synchronized(lock) {
- if (closed) {
- return
- }
- if (requestProcessor !== _requestProcessor) {
- return
- }
- }
- resubmit()
+ graphLoop.invalidate()
}
override fun onGraphError(graphStateError: GraphStateError) {
@@ -251,57 +219,19 @@
}
}
- override fun startRepeating(request: Request) {
- synchronized(lock) {
- if (closed) return
- currentRepeatingRequest = request
- repeatingQueue.add(request)
- debug { "startRepeating with ${request.formatForLogs()}" }
- coroutineMutex.withLockLaunch(graphScope) { tryStartRepeating() }
- }
- }
+ override fun submit(request: Request): Boolean = submit(listOf(request))
- override fun stopRepeating() {
- val processor: GraphRequestProcessor?
- synchronized(lock) {
- processor = _requestProcessor
- repeatingQueue.clear()
- currentRepeatingRequest = null
- coroutineMutex.withLockLaunch(graphScope) {
- Debug.traceStart { "$this#stopRepeating" }
- // Start with requests that have already been submitted
- if (processor != null) {
- synchronized(processor) { processor.stopRepeating() }
- }
- Debug.traceStop()
+ override fun submit(requests: List<Request>): Boolean {
+ val reprocessingRequest = requests.firstOrNull { it.inputRequest != null }
+ if (reprocessingRequest != null) {
+ checkNotNull(cameraGraphConfig.input) {
+ "Cannot submit $reprocessingRequest with input request " +
+ "${reprocessingRequest.inputRequest} to $this because CameraGraph was not " +
+ "configured to support reprocessing"
}
}
- }
- override fun submit(request: Request) {
- submit(listOf(request))
- }
-
- override fun submit(requests: List<Request>) {
- requests
- .firstOrNull { it.inputRequest != null }
- ?.let {
- check(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- "Reprocessing not supported on Android ${Build.VERSION.SDK_INT} devices"
- }
- checkNotNull(cameraGraphConfig.input) {
- "Cannot submit request $it with input request ${it.inputRequest} " +
- "to $this because CameraGraph was not configured to support reprocessing"
- }
- }
- synchronized(lock) {
- if (closed) {
- graphScope.launch(threads.lightweightDispatcher) { abortBurst(requests) }
- return
- }
- submitQueue.add(requests)
- }
- graphScope.launch(threads.lightweightDispatcher) { submitLoop() }
+ return graphLoop.submit(requests)
}
/**
@@ -310,285 +240,18 @@
* false. Otherwise, the method tries to submit the provided [parameters] and suspends until it
* finishes.
*/
- override suspend fun trySubmit(parameters: Map<*, Any?>): Boolean =
- withContext(threads.lightweightDispatcher) {
- val processor: GraphRequestProcessor?
- val request: Request
- val requiredParameters: MutableMap<Any, Any?> = mutableMapOf()
- var deferredResult: CompletableDeferred<Boolean>? = null
- synchronized(lock) {
- if (closed) return@withContext false
- processor = _requestProcessor
- // If there is no current repeating request and no repeating requests are in the
- // queue (i.e., startRepeating wasn't called before the 3A methods), we should just
- // fail immediately.
- if (currentRepeatingRequest == null) {
- return@withContext false
- }
- request = currentRepeatingRequest as Request
- requiredParameters.putAllMetadata(parameters.toMutableMap())
- graphState3A.writeTo(requiredParameters)
- requiredParameters.putAllMetadata(cameraGraphConfig.requiredParameters)
- if (processor == null) {
- // If a previous set of parameters haven't been submitted yet, consider it stale
- pendingParametersDeferred?.complete(false)
- debug { "Holding parameters to be submitted later" }
- deferredResult = CompletableDeferred<Boolean>()
- pendingParametersDeferred = deferredResult
- pendingParameters = requiredParameters
- }
- }
- return@withContext when {
- processor == null -> deferredResult?.await() == true
- else ->
- processor.submit(
- isRepeating = false,
- requests = listOf(request),
- defaultParameters = cameraGraphConfig.defaultParameters,
- requiredParameters = requiredParameters,
- listeners = graphListeners
- )
- }
- }
-
- override fun hasRepeatingRequest() = synchronized(lock) { currentRepeatingRequest != null }
+ override fun submit(parameters: Map<*, Any?>): Boolean = graphLoop.submit(parameters)
override fun invalidate() {
- // Invalidate is only used for updates to internal state (listeners, parameters, etc) and
- // should not (currently) attempt to resubmit the normal request queue.
- graphScope.launch(threads.lightweightDispatcher) { tryStartRepeating() }
+ graphLoop.invalidate()
}
override fun abort() {
- val processor: GraphRequestProcessor?
- val requests: List<List<Request>>
- synchronized(lock) {
- processor = _requestProcessor
- requests = submitQueue.toList()
- submitQueue.clear()
- }
- graphScope.launch(threads.lightweightDispatcher) {
- Debug.traceStart { "$this#abort" }
- // Start with requests that have already been submitted
- if (processor != null) {
- synchronized(processor) { processor.abortCaptures() }
- }
- // Then abort requests that have not been submitted
- for (burst in requests) {
- abortBurst(burst)
- }
- Debug.traceStop()
- }
+ graphLoop.abort()
}
override fun close() {
- val processor: GraphRequestProcessor?
- synchronized(lock) {
- if (closed) {
- return
- }
- closed = true
- processor = _requestProcessor
- _requestProcessor = null
- }
- processor?.close()
- abort()
- }
-
- private fun resubmit() {
- graphScope.launch(threads.lightweightDispatcher) {
- tryStartRepeating()
- submitLoop()
- }
- }
-
- private fun abortBurst(requests: List<Request>) {
- for (request in requests) {
- abortRequest(request)
- }
- }
-
- private fun abortRequest(request: Request) {
- for (listenerIdx in graphListeners.indices) {
- graphListeners[listenerIdx].onAborted(request)
- }
- for (listenerIdx in request.listeners.indices) {
- request.listeners[listenerIdx].onAborted(request)
- }
- }
-
- private fun tryStartRepeating() =
- synchronized(tryStartRepeatingExecutionLock) {
- val processor: GraphRequestProcessor
- val requests = mutableListOf<Request>()
- var shouldRetryRequests = false
- synchronized(lock) {
- if (closed || _requestProcessor == null) return
- processor = _requestProcessor!!
- if (repeatingQueue.isNotEmpty()) {
- requests.addAll(repeatingQueue)
- repeatingQueue.clear()
- shouldRetryRequests = true
- } else {
- currentRepeatingRequest?.let { requests.add(it) }
- }
- }
- if (requests.isEmpty()) return
- Debug.traceStart { "$this#startRepeating" }
- var succeededIndex = -1
- synchronized(processor) {
- // Here an important optimization is applied. Newer repeating requests should always
- // supersede older ones. Instead of going from oldest request to newest, we can
- // start
- // from the newest request and immediately break when a request submission succeeds.
- for ((index, request) in requests.reversed().withIndex()) {
- val requiredParameters = mutableMapOf<Any, Any?>()
- graphState3A.writeTo(requiredParameters)
- requiredParameters.putAllMetadata(cameraGraphConfig.requiredParameters)
- if (
- processor.submit(
- isRepeating = true,
- requests = listOf(request),
- defaultParameters = cameraGraphConfig.defaultParameters,
- requiredParameters = requiredParameters,
- listeners = graphProcessorRepeatingListeners,
- )
- ) {
- // ONLY update the current repeating request if the update succeeds
- synchronized(lock) {
- if (processor === _requestProcessor) {
- trySubmitPendingParameters(processor, request)
- }
- }
- succeededIndex = index
- break
- }
- }
- }
- Debug.traceStop()
- if (shouldRetryRequests) {
- synchronized(lock) {
- // We should only retry the requests newer than the succeeded request, since the
- // succeeded request would prevail over the preceding requests that failed.
- val requestsToRetry = requests.slice(succeededIndex + 1 until requests.size)
- // We might have new repeating requests at this point, and these requests to
- // retry
- // should be placed in the front in order to preserve FIFO order.
- repeatingQueue.addAll(0, requestsToRetry)
- }
- }
- }
-
- @GuardedBy("lock")
- private fun trySubmitPendingParameters(processor: GraphRequestProcessor, request: Request) {
- val parameters = pendingParameters
- val deferred = pendingParametersDeferred
- if (parameters != null && deferred != null) {
- val resubmitResult =
- processor.submit(
- isRepeating = false,
- requests = listOf(request),
- defaultParameters = cameraGraphConfig.defaultParameters,
- requiredParameters = parameters,
- listeners = graphListeners
- )
- deferred.complete(resubmitResult)
- pendingParameters = null
- pendingParametersDeferred = null
- }
- }
-
- private fun submitLoop() {
- if (Camera2Quirks.shouldWaitForRepeatingBeforeCapture() && hasRepeatingRequest()) {
- debug {
- "Quirk: Waiting for 10 repeating requests to complete before submitting requests"
- }
- if (!repeatingRequestsCompleted.await(2, TimeUnit.SECONDS)) {
- warn { "Failed to wait for 10 repeating requests to complete after 2 seconds" }
- }
- }
- var burst: List<Request>
- var processor: GraphRequestProcessor
- synchronized(lock) {
- if (closed) return
- if (submitting) {
- dirty = true
- return
- }
- val nullableProcessor = _requestProcessor
- val nullableBurst = submitQueue.firstOrNull()
- if (nullableProcessor == null || nullableBurst == null) {
- return
- }
- processor = nullableProcessor
- burst = nullableBurst
- submitting = true
- }
- while (true) {
- var submitted = false
- Debug.traceStart { "$this#submit" }
- try {
- submitted =
- synchronized(processor) {
- val requiredParameters = mutableMapOf<Any, Any?>()
- if (
- cameraGraphConfig.defaultParameters[
- CameraPipeKeys.ignore3ARequiredParameters] == true ||
- cameraGraphConfig.requiredParameters[
- CameraPipeKeys.ignore3ARequiredParameters] == true
- ) {
- info {
- "${CameraPipeKeys.ignore3ARequiredParameters} is set to true, " +
- "ignoring 3A required parameters"
- }
- } else {
- graphState3A.writeTo(requiredParameters)
- }
- requiredParameters.putAllMetadata(cameraGraphConfig.requiredParameters)
- processor.submit(
- isRepeating = false,
- requests = burst,
- defaultParameters = cameraGraphConfig.defaultParameters,
- requiredParameters = requiredParameters,
- listeners = graphListeners
- )
- }
- } finally {
- Debug.traceStop()
- synchronized(lock) {
- if (submitted) {
- // submitQueue can potentially be cleared by abort() before entering here.
- check(submitQueue.isEmpty() || submitQueue.removeAt(0) === burst)
- val nullableBurst = submitQueue.firstOrNull()
- if (nullableBurst == null) {
- dirty = false
- submitting = false
- return
- }
- burst = nullableBurst
- } else if (!dirty) {
- debug { "Failed to submit $burst, and the queue is not dirty." }
- // If we did not submit, and we are also not dirty, then exit the loop
- submitting = false
- return
- } else {
- debug {
- "Failed to submit $burst but the request queue or processor is " +
- "dirty. Clearing dirty flag and attempting retry."
- }
- dirty = false
- // One possible situation is that the _requestProcessor was replaced or
- // set to null. If this happens, try to update the requestProcessor we
- // are currently using. If the current request processor is null, then
- // we cannot submit anyways.
- val nullableProcessor = _requestProcessor
- if (nullableProcessor != null) {
- processor = nullableProcessor
- }
- }
- }
- }
- }
+ graphLoop.close()
}
override fun toString(): String = "GraphProcessor(cameraGraph: $cameraGraphId)"
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphRequestProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphRequestProcessor.kt
index 0b2c1a3..cf9d3c2 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphRequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphRequestProcessor.kt
@@ -104,10 +104,10 @@
captureSequenceProcessor.stopRepeating()
}
- internal fun close() {
+ internal suspend fun shutdown() {
Log.debug { "Closing $this" }
if (closed.compareAndSet(expect = false, update = true)) {
- captureSequenceProcessor.close()
+ captureSequenceProcessor.shutdown()
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Listener3A.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Listener3A.kt
index 5cb02d3..f3477ffb 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Listener3A.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Listener3A.kt
@@ -33,7 +33,7 @@
* for desired 3A state changes.
*/
@CameraGraphScope
-internal class Listener3A @Inject constructor() : Request.Listener {
+internal class Listener3A @Inject constructor() : Request.Listener, GraphLoop.Listener {
private val listeners: CopyOnWriteArrayList<Result3AStateListener> = CopyOnWriteArrayList()
override fun onRequestSequenceCreated(requestMetadata: RequestMetadata) {
@@ -66,12 +66,6 @@
listeners.remove(listener)
}
- internal fun onStopRepeating() {
- for (listener in listeners) {
- listener.onRequestSequenceStopped()
- }
- }
-
private fun updateListeners(requestNumber: RequestNumber, metadata: FrameMetadata) {
for (listener in listeners) {
if (listener.update(requestNumber, metadata)) {
@@ -79,4 +73,22 @@
}
}
}
+
+ override fun onStopRepeating() {
+ for (listener in listeners) {
+ listener.onStopRepeating()
+ }
+ }
+
+ override fun onGraphStopped() {
+ for (listener in listeners) {
+ listener.onStopRepeating()
+ }
+ }
+
+ override fun onGraphShutdown() {
+ for (listener in listeners) {
+ listener.onStopRepeating()
+ }
+ }
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Result3AStateListener.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Result3AStateListener.kt
index 17aca1f..d338a9e 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Result3AStateListener.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Result3AStateListener.kt
@@ -38,12 +38,10 @@
* This update method can be called multiple times as we get newer [CaptureResult]s from the camera
* device. This class also exposes a [Deferred] to query the status of desired state.
*/
-internal interface Result3AStateListener {
+internal interface Result3AStateListener : GraphLoop.Listener {
fun onRequestSequenceCreated(requestNumber: RequestNumber)
fun update(requestNumber: RequestNumber, frameMetadata: FrameMetadata): Boolean
-
- fun onRequestSequenceStopped()
}
internal class Result3AStateListenerImpl(
@@ -133,12 +131,16 @@
return true
}
- override fun onRequestSequenceStopped() {
+ override fun onStopRepeating() {
_result.complete(Result3A(Result3A.Status.SUBMIT_CANCELLED))
}
- fun getDeferredResult(): Deferred<Result3A> {
- return _result
+ override fun onGraphStopped() {
+ _result.complete(Result3A(Result3A.Status.SUBMIT_CANCELLED))
+ }
+
+ override fun onGraphShutdown() {
+ _result.complete(Result3A(Result3A.Status.SUBMIT_CANCELLED))
}
}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImplTest.kt
index 15db1bd..8c3b7bc 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphSessionImplTest.kt
@@ -57,7 +57,11 @@
private val graphState3A = GraphState3A()
private val listener3A = Listener3A()
private val graphProcessor =
- FakeGraphProcessor(graphState3A = graphState3A, defaultListeners = listOf(listener3A))
+ FakeGraphProcessor(
+ graphState3A = graphState3A,
+ graphListener3A = listener3A,
+ defaultListeners = listOf(listener3A)
+ )
private val fakeCaptureSequenceProcessor = FakeCaptureSequenceProcessor()
private val fakeGraphRequestProcessor = GraphRequestProcessor.from(fakeCaptureSequenceProcessor)
private val controller3A =
@@ -102,12 +106,16 @@
session.startRepeating(Request(streams = listOf(StreamId(1))))
graphProcessor.invalidate()
- val result = session.lock3A(aeLockBehavior = Lock3ABehavior.IMMEDIATE)
+ val deferred = session.lock3A(aeLockBehavior = Lock3ABehavior.IMMEDIATE)
+
+ assertThat(deferred.isCompleted).isFalse()
// Don't return any results to simulate that the 3A conditions haven't been met, but the
// app calls stopRepeating(). In which case, we should fail here with SUBMIT_CANCELLED.
session.stopRepeating()
- assertThat(result.await().status).isEqualTo(Result3A.Status.SUBMIT_CANCELLED)
+ assertThat(deferred.isCompleted).isTrue()
+ val result = deferred.await()
+ assertThat(result.status).isEqualTo(Result3A.Status.SUBMIT_CANCELLED)
}
@Test
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CaptureLimiterTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CaptureLimiterTest.kt
new file mode 100644
index 0000000..67b2c66
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CaptureLimiterTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.graph
+
+import android.os.Build
+import androidx.camera.camera2.pipe.CameraGraphId
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.testing.FakeFrameInfo
+import androidx.camera.camera2.pipe.testing.FakeRequestMetadata
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.robolectric.annotation.Config
+
+@RunWith(JUnit4::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class CaptureLimiterTest {
+ private val testScope = TestScope()
+ private val testDispatcher = StandardTestDispatcher(testScope.testScheduler)
+ private val graphState3A = GraphState3A()
+
+ private val fakeRequestMetadata = FakeRequestMetadata()
+ private val fakeFrameInfo = FakeFrameInfo()
+
+ private val captureLimiter = CaptureLimiter(3)
+ private val cameraGraphId = CameraGraphId.nextId()
+
+ private val graphLoop =
+ GraphLoop(
+ cameraGraphId = cameraGraphId,
+ defaultParameters = emptyMap<Any, Any?>(),
+ requiredParameters = emptyMap<Any, Any?>(),
+ graphListeners = listOf(),
+ graphState3A = graphState3A,
+ listeners = listOf(captureLimiter),
+ shutdownScope = testScope,
+ dispatcher = testDispatcher,
+ )
+
+ init {
+ captureLimiter.graphLoop = graphLoop
+ }
+
+ @Test
+ fun captureLimiterEnablesCaptureProcessingAfterFrameCount() {
+ assertThat(graphLoop.captureProcessingEnabled).isFalse()
+
+ // ACT
+ simulateFrames(2)
+ assertThat(graphLoop.captureProcessingEnabled).isFalse()
+
+ // ACT
+ simulateFrames(1)
+ assertThat(graphLoop.captureProcessingEnabled).isTrue()
+ }
+
+ @Test
+ fun captureLimiterResetsAfterGraphProcessorIsRemoved() {
+ simulateFrames(3)
+ assertThat(graphLoop.captureProcessingEnabled).isTrue()
+
+ // ACT
+ graphLoop.requestProcessor = null
+
+ assertThat(graphLoop.captureProcessingEnabled).isFalse()
+ simulateFrames(3)
+ assertThat(graphLoop.captureProcessingEnabled).isTrue()
+ }
+
+ @Test
+ fun captureLimiterPermanentlyDisablesAfterClose() =
+ testScope.runTest {
+ simulateFrames(3)
+ assertThat(graphLoop.captureProcessingEnabled).isTrue()
+
+ // ACT
+ graphLoop.close()
+ assertThat(graphLoop.captureProcessingEnabled).isFalse()
+ simulateFrames(3)
+ assertThat(graphLoop.captureProcessingEnabled).isFalse()
+ }
+
+ private fun simulateFrames(count: Long) {
+ for (i in 1L..count) {
+ captureLimiter.onComplete(fakeRequestMetadata, FrameNumber(i), fakeFrameInfo)
+ }
+ }
+}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3ASubmit3ATest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3ASubmit3ATest.kt
index 3af94c5..3d72cac 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3ASubmit3ATest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3ASubmit3ATest.kt
@@ -34,6 +34,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Test
@@ -231,9 +232,12 @@
// There are different conditions that can lead to the request processor not being able
// to successfully submit the desired request. For this test we are closing the processor.
graphProcessor.close()
+ advanceUntilIdle()
// Since the request processor is closed the submit3A method call will fail.
- val result = controller3A.submit3A(aeMode = AeMode.ON_ALWAYS_FLASH).await()
+ val deferred = controller3A.submit3A(aeMode = AeMode.ON_ALWAYS_FLASH)
+ assertThat(deferred.isCompleted)
+ val result = deferred.await()
assertThat(result.frameMetadata).isNull()
assertThat(result.status).isEqualTo(Result3A.Status.SUBMIT_FAILED)
}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3AUpdate3ATest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3AUpdate3ATest.kt
index 0b14071..93f81ba 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3AUpdate3ATest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/Controller3AUpdate3ATest.kt
@@ -253,6 +253,6 @@
private fun initGraphProcessor() {
graphProcessor.onGraphStarted(fakeGraphRequestProcessor)
- graphProcessor.startRepeating(Request(streams = listOf(StreamId(1))))
+ graphProcessor.repeatingRequest = Request(streams = listOf(StreamId(1)))
}
}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphLoopTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphLoopTest.kt
index 419d94e..728d859 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphLoopTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphLoopTest.kt
@@ -18,10 +18,12 @@
import android.os.Build
import android.view.Surface
+import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.CaptureSequence
import androidx.camera.camera2.pipe.CaptureSequenceProcessor
import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.Result3A
import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
import androidx.camera.camera2.pipe.testing.FakeCaptureSequence
@@ -30,7 +32,9 @@
import androidx.testutils.assertThrows
import com.google.common.truth.Truth.assertThat
import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
@@ -50,8 +54,10 @@
class GraphLoopTest {
private val testScope = TestScope()
private val testDispatcher = StandardTestDispatcher(testScope.testScheduler)
+ private val shutdownScope = CoroutineScope(testDispatcher)
private val graphState3A = GraphState3A()
+ private val listener3A = Listener3A()
private val defaultParameters = emptyMap<Any, Any?>()
private val requiredParameters = emptyMap<Any, Any?>()
private val mockListener: Request.Listener = mock<Request.Listener>()
@@ -76,19 +82,24 @@
private val request1 = Request(streams = listOf(stream1))
private val request2 = Request(streams = listOf(stream2))
private val request3 = Request(streams = listOf(stream1, stream2))
+ private val cameraGraphId = CameraGraphId.nextId()
private val graphLoop =
GraphLoop(
+ cameraGraphId = cameraGraphId,
defaultParameters = defaultParameters,
requiredParameters = requiredParameters,
- listeners = listOf(mockListener),
+ graphListeners = listOf(mockListener),
graphState3A = graphState3A,
+ listeners = listOf(listener3A),
+ shutdownScope = shutdownScope,
dispatcher = testDispatcher,
)
@After
fun teardown() {
fakeSurfaces.close()
+ shutdownScope.cancel()
}
@Test
@@ -238,7 +249,7 @@
fun changingRequestProcessorsReIssuesCaptureRequests() =
testScope.runTest {
graphLoop.requestProcessor = grp1
- csp1.close() // Reject requests
+ csp1.shutdown() // reject incoming requests
graphLoop.submit(listOf(request1))
graphLoop.submit(listOf(request2))
advanceUntilIdle()
@@ -257,7 +268,7 @@
fun capturesThatFailCanBeRetried() =
testScope.runTest {
graphLoop.requestProcessor = grp1
- csp1.close() // reject incoming requests
+ csp1.shutdown() // reject incoming requests
graphLoop.repeatingRequest = request1
advanceUntilIdle()
@@ -456,10 +467,13 @@
testScope.runTest {
val gl =
GraphLoop(
+ cameraGraphId = cameraGraphId,
defaultParameters = mapOf<Any, Any?>(TEST_KEY to 10),
requiredParameters = requiredParameters,
- listeners = listOf(mockListener),
+ graphListeners = listOf(mockListener),
graphState3A = graphState3A,
+ listeners = listOf(listener3A),
+ shutdownScope = shutdownScope,
dispatcher = testDispatcher,
)
@@ -491,10 +505,13 @@
testScope.runTest {
val gl =
GraphLoop(
- defaultParameters = emptyMap(),
+ cameraGraphId = cameraGraphId,
+ defaultParameters = emptyMap<Any, Any?>(),
requiredParameters = mapOf<Any, Any?>(TEST_KEY to 10),
- listeners = listOf(mockListener),
+ graphListeners = listOf(mockListener),
graphState3A = graphState3A,
+ listeners = listOf(listener3A),
+ shutdownScope = shutdownScope,
dispatcher = testDispatcher,
)
@@ -525,7 +542,7 @@
fun requestsSubmittedToClosedRequestProcessorAreEnqueuedToTheNextOne() =
testScope.runTest {
graphLoop.requestProcessor = grp1
- grp1.close()
+ grp1.shutdown()
graphLoop.repeatingRequest = request1
graphLoop.submit(mapOf<Any, Any?>(TEST_KEY to 42))
graphLoop.submit(listOf(request2))
@@ -623,6 +640,189 @@
.contains("Test Exception")
}
+ @Test
+ fun stopRepeatingCancelsTriggers() =
+ testScope.runTest {
+ val listener = Result3AStateListenerImpl({ _ -> true }, 10, 1_000_000_000)
+ listener3A.addListener(listener)
+ assertThat(listener.result.isCompleted).isFalse()
+
+ graphLoop.repeatingRequest = null
+
+ assertThat(listener.result.isCompleted).isTrue()
+ assertThat(listener.result.await().status).isEqualTo(Result3A.Status.SUBMIT_CANCELLED)
+ }
+
+ @Test
+ fun clearingRequestProcessorCancelsTriggers() =
+ testScope.runTest {
+ // Setup the graph loop so that the repeating request and trigger are enqueued before
+ // the graphRequestProcessor is configured. Assert that the listener is not invoked
+ // until after the requestProcessor is stopped.
+ graphLoop.repeatingRequest = request1
+ val listener = Result3AStateListenerImpl({ _ -> true }, 10, 1_000_000_000)
+ listener3A.addListener(listener)
+ graphLoop.submit(mapOf<Any, Any?>(TEST_KEY to 42))
+ graphLoop.requestProcessor = grp1
+ advanceUntilIdle()
+
+ assertThat(listener.result.isCompleted).isFalse()
+
+ graphLoop.requestProcessor = null
+ advanceUntilIdle()
+
+ assertThat(listener.result.isCompleted).isTrue()
+ assertThat(listener.result.await().status).isEqualTo(Result3A.Status.SUBMIT_CANCELLED)
+ }
+
+ @Test
+ fun shutdownRequestProcessorCancelsTriggers() =
+ testScope.runTest {
+ // Arrange
+ val listener = Result3AStateListenerImpl({ _ -> true }, 10, 1_000_000_000)
+ listener3A.addListener(listener)
+
+ // Act
+ graphLoop.requestProcessor = null
+
+ // Assert
+ assertThat(listener.result.isCompleted).isTrue()
+ assertThat(listener.result.await().status).isEqualTo(Result3A.Status.SUBMIT_CANCELLED)
+ }
+
+ @Test
+ fun swappingRequestProcessorsDoesNotCancelTriggers() {
+ testScope.runTest {
+ // Arrange
+
+ // Setup the graph loop so that the repeating request and trigger are enqueued before
+ // the graphRequestProcessor is configured. Assert that the listener is not invoked
+ // until after the requestProcessor is stopped.
+ graphLoop.requestProcessor = grp1
+ val listener = Result3AStateListenerImpl({ _ -> true }, 10, 1_000_000_000)
+ listener3A.addListener(listener)
+ graphLoop.requestProcessor = grp2 // Does not cancel trigger
+ advanceUntilIdle()
+ assertThat(listener.result.isCompleted).isFalse()
+
+ // Act
+ graphLoop.requestProcessor = null // Cancel triggers
+
+ // Assert
+ assertThat(listener.result.isCompleted).isTrue()
+ assertThat(listener.result.await().status).isEqualTo(Result3A.Status.SUBMIT_CANCELLED)
+ }
+ }
+
+ @Test
+ fun pausingCaptureProcessingPreventsCaptureRequests() =
+ testScope.runTest {
+ // Arrange
+ graphLoop.requestProcessor = grp1
+ graphLoop.captureProcessingEnabled = false // Disable captureProcessing
+
+ // Act
+ graphLoop.submit(listOf(request1))
+ graphLoop.submit(listOf(request2))
+ advanceUntilIdle()
+
+ // Assert: Events are not processed
+ assertThat(csp1.events.size).isEqualTo(0)
+ }
+
+ @Test
+ fun resumingCaptureProcessingResumesCaptureRequests() =
+ testScope.runTest {
+ // Arrange
+ graphLoop.requestProcessor = grp1
+ graphLoop.captureProcessingEnabled = false // Disable captureProcessing
+
+ // Act
+ graphLoop.submit(listOf(request1))
+ graphLoop.submit(listOf(request2))
+ advanceUntilIdle()
+ graphLoop.captureProcessingEnabled = true // Enable processing
+ advanceUntilIdle()
+
+ // Assert: Events are not processed
+ assertThat(csp1.events.size).isEqualTo(2)
+
+ assertThat(csp1.events[0].isCapture).isTrue()
+ assertThat(csp1.events[0].requests).containsExactly(request1)
+
+ assertThat(csp1.events[1].isCapture).isTrue()
+ assertThat(csp1.events[1].requests).containsExactly(request2)
+ }
+
+ @Test
+ fun disablingCaptureProcessingAllowsRepeatingRequests() =
+ testScope.runTest {
+ // Arrange
+ graphLoop.requestProcessor = grp1
+
+ // Act
+ graphLoop.captureProcessingEnabled = false // Disable captureProcessing
+ graphLoop.repeatingRequest = request1
+ advanceUntilIdle()
+
+ // Assert
+ assertThat(csp1.events.size).isEqualTo(1)
+ assertThat(csp1.events[0].isRepeating).isTrue()
+ assertThat(csp1.events[0].requests).containsExactly(request1)
+ }
+
+ @Test
+ fun settingNullForRequestProcessorAfterCloseDoesNotCrash() =
+ testScope.runTest {
+ // Arrange
+ graphLoop.requestProcessor = grp1
+ graphLoop.close()
+
+ // Act
+ graphLoop.requestProcessor = null
+ advanceUntilIdle()
+
+ // Assert: does not crash, and only Close is invoked.
+ assertThat(csp1.events.size).isEqualTo(1)
+ assertThat(csp1.events[0].isClose).isTrue()
+ }
+
+ @Test
+ fun settingRequestProcessorAfterCloseCausesRequestProcessorToBeShutdown() =
+ testScope.runTest {
+ // Arrange
+ graphLoop.close()
+
+ // Act
+ graphLoop.requestProcessor = grp1
+ advanceUntilIdle()
+
+ // Assert: Does not crash, and request processor is closed.
+ assertThat(csp1.events.size).isEqualTo(1)
+ assertThat(csp1.events[0].isClose).isTrue()
+ }
+
+ @Test
+ fun settingRequestProcessorAfterShutdownCausesRequestProcessorToBeShutdown() =
+ testScope.runTest {
+ // Arrange
+
+ graphLoop.requestProcessor = grp1
+ graphLoop.close()
+ advanceUntilIdle() // Shutdown fully completes
+
+ // Act
+ graphLoop.requestProcessor = grp2
+ advanceUntilIdle()
+
+ // Assert: Does not crash, and request processor is closed.
+ assertThat(csp1.events.size).isEqualTo(1)
+ assertThat(csp1.events[0].isClose).isTrue()
+
+ assertThat(csp2.events.size).isEqualTo(1)
+ assertThat(csp2.events[0].isClose).isTrue()
+ }
+
private val SimpleCSP.SimpleCSPEvent.requests: List<Request>
get() = (this as SimpleCSP.Submit).captureSequence.captureRequestList
@@ -686,7 +886,7 @@
events.add(StopRepeating)
}
- override fun close() {
+ override suspend fun shutdown() {
closed = true
events.add(Close)
}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt
index d95aa17..13e29b9 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt
@@ -33,9 +33,9 @@
import androidx.camera.camera2.pipe.testing.FakeRequestListener
import androidx.camera.camera2.pipe.testing.FakeThreads
import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
+import androidx.testutils.assertThrows
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
@@ -54,6 +54,7 @@
internal class GraphProcessorTest {
private val globalListener = FakeRequestListener()
private val graphState3A = GraphState3A()
+ private val graphListener3A = Listener3A()
private val streamId = StreamId(0)
private val surfaceMap = mapOf(streamId to Surface(SurfaceTexture(1)))
@@ -82,7 +83,7 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
graphProcessor.onGraphStarted(graphRequestProcessor1)
@@ -104,7 +105,7 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
@@ -128,7 +129,7 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
@@ -156,7 +157,7 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
@@ -176,7 +177,7 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
@@ -208,7 +209,7 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
@@ -246,13 +247,13 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
graphProcessor.onGraphStarted(graphRequestProcessor1)
- graphProcessor.startRepeating(request1)
- graphProcessor.startRepeating(request2)
+ graphProcessor.repeatingRequest = request1
+ graphProcessor.repeatingRequest = request2
advanceUntilIdle()
val event =
@@ -271,19 +272,19 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
fakeProcessor1.rejectRequests = true
graphProcessor.onGraphStarted(graphRequestProcessor1)
- graphProcessor.startRepeating(request1)
+ graphProcessor.repeatingRequest = request1
val event1 = fakeProcessor1.nextEvent()
assertThat(event1.rejected).isTrue()
assertThat(event1.requestSequence!!.captureRequestList[0]).isSameInstanceAs(request1)
- graphProcessor.startRepeating(request2)
+ graphProcessor.repeatingRequest = request2
val event2 = fakeProcessor1.nextEvent()
assertThat(event2.rejected).isTrue()
fakeProcessor1.awaitEvent(request = request2) {
@@ -291,7 +292,7 @@
}
fakeProcessor1.rejectRequests = false
- graphProcessor.onGraphStarted(graphRequestProcessor1)
+ graphProcessor.invalidate()
fakeProcessor1.awaitEvent(request = request2) {
it.submit && it.requestSequence?.repeating == true
@@ -306,12 +307,12 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
graphProcessor.onGraphStarted(graphRequestProcessor1)
- graphProcessor.startRepeating(request1)
+ graphProcessor.repeatingRequest = request1
advanceUntilIdle()
fakeProcessor1.awaitEvent(request = request1) {
@@ -334,13 +335,13 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
fakeProcessor1.rejectRequests = true
graphProcessor.onGraphStarted(graphRequestProcessor1)
- graphProcessor.startRepeating(request1)
+ graphProcessor.repeatingRequest = request1
fakeProcessor1.awaitEvent(request = request1) { it.rejected }
graphProcessor.onGraphStarted(graphRequestProcessor2)
@@ -357,11 +358,11 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
- graphProcessor.startRepeating(request1)
+ graphProcessor.repeatingRequest = request1
graphProcessor.submit(request2)
delay(50)
@@ -393,11 +394,11 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
- graphProcessor.startRepeating(request1)
+ graphProcessor.repeatingRequest = request1
graphProcessor.submit(request2)
// Abort queued and in-flight requests.
@@ -426,14 +427,15 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
graphProcessor.close()
+ advanceUntilIdle()
// Abort queued and in-flight requests.
- graphProcessor.onGraphStarted(graphRequestProcessor1)
- graphProcessor.startRepeating(request1)
+ // graphProcessor.onGraphStarted(graphRequestProcessor1)
+ graphProcessor.repeatingRequest = request1
graphProcessor.submit(request2)
val abortEvent1 =
@@ -441,8 +443,6 @@
val abortEvent2 = requestListener2.onAbortedFlow.first()
assertThat(abortEvent1).isNull()
assertThat(abortEvent2.request).isSameInstanceAs(request2)
-
- assertThat(fakeProcessor1.nextEvent().close).isTrue()
}
@Test
@@ -453,23 +453,25 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
// Submit a repeating request first to make sure we have one in progress.
- graphProcessor.startRepeating(request1)
+ graphProcessor.repeatingRequest = request1
advanceUntilIdle()
- val result = async {
- graphProcessor.trySubmit(mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AE_LOCK to false))
- }
+ graphProcessor.submit(mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AE_LOCK to false))
advanceUntilIdle()
graphProcessor.onGraphStarted(graphRequestProcessor1)
advanceUntilIdle()
-
- assertThat(result.await()).isTrue()
+ val event1 = fakeProcessor1.nextEvent()
+ assertThat(event1.requestSequence?.repeating).isTrue()
+ val event2 = fakeProcessor1.nextEvent()
+ assertThat(event2.requestSequence?.repeating).isFalse()
+ assertThat(event2.requestSequence?.requestMetadata?.get(request1)?.get(CONTROL_AE_LOCK))
+ .isFalse()
}
@Test
@@ -480,21 +482,14 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
// Submit a repeating request first to make sure we have one in progress.
- graphProcessor.startRepeating(request1)
- advanceUntilIdle()
-
- val result1 = async {
- graphProcessor.trySubmit(mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AE_LOCK to false))
- }
- advanceUntilIdle()
- val result2 = async {
- graphProcessor.trySubmit(mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AE_LOCK to true))
- }
+ graphProcessor.repeatingRequest = request1
+ graphProcessor.submit(mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AE_LOCK to false))
+ graphProcessor.submit(mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AE_LOCK to true))
advanceUntilIdle()
graphProcessor.onGraphStarted(graphRequestProcessor1)
@@ -505,10 +500,11 @@
val event2 = fakeProcessor1.nextEvent()
assertThat(event2.requestSequence?.repeating).isFalse()
assertThat(event2.requestSequence?.requestMetadata?.get(request1)?.get(CONTROL_AE_LOCK))
+ .isFalse()
+ val event3 = fakeProcessor1.nextEvent()
+ assertThat(event3.requestSequence?.repeating).isFalse()
+ assertThat(event3.requestSequence?.requestMetadata?.get(request1)?.get(CONTROL_AE_LOCK))
.isTrue()
-
- assertThat(result1.await()).isFalse()
- assertThat(result2.await()).isTrue()
}
@Test
@@ -519,16 +515,16 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
graphProcessor.onGraphStarted(graphRequestProcessor1)
advanceUntilIdle()
- val result =
- graphProcessor.trySubmit(mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AE_LOCK to true))
- assertThat(result).isFalse()
+ assertThrows<IllegalStateException> {
+ graphProcessor.submit(mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AE_LOCK to true))
+ }
}
@Test
@@ -539,7 +535,7 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
assertThat(graphProcessor.graphState.value).isEqualTo(GraphStateStopped)
@@ -559,7 +555,7 @@
CameraGraphId.nextId(),
FakeGraphConfigs.graphConfig,
graphState3A,
- this,
+ graphListener3A,
arrayListOf(globalListener)
)
assertThat(graphProcessor.graphState.value).isEqualTo(GraphStateStopped)
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphTestContext.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphTestContext.kt
index 93d4a6e..dbeb67d 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphTestContext.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphTestContext.kt
@@ -33,7 +33,7 @@
init {
captureSequenceProcessor.surfaceMap = surfaceMap
graphProcessor.onGraphStarted(graphRequestProcessor)
- graphProcessor.startRepeating(Request(streams = listOf(streamId)))
+ graphProcessor.repeatingRequest = Request(streams = listOf(streamId))
}
override fun close() {
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
index 0137588..4205aef 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
@@ -27,14 +27,17 @@
import androidx.camera.camera2.pipe.graph.GraphProcessor
import androidx.camera.camera2.pipe.graph.GraphRequestProcessor
import androidx.camera.camera2.pipe.graph.GraphState3A
+import androidx.camera.camera2.pipe.graph.Listener3A
import androidx.camera.camera2.pipe.putAllMetadata
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.runBlocking
/** Fake implementation of a [GraphProcessor] for tests. */
internal class FakeGraphProcessor(
val graphState3A: GraphState3A = GraphState3A(),
+ val graphListener3A: Listener3A = Listener3A(),
val defaultParameters: Map<*, Any?> = emptyMap<Any, Any?>(),
val defaultListeners: List<Request.Listener> = emptyList()
) : GraphProcessor, GraphListener {
@@ -44,8 +47,15 @@
var closed = false
private set
- var repeatingRequest: Request? = null
- private set
+ private var _repeatingRequest: Request? = null
+ override var repeatingRequest: Request?
+ get() = _repeatingRequest
+ set(value) {
+ _repeatingRequest = value
+ if (value == null) {
+ graphListener3A.onStopRepeating()
+ }
+ }
val requestQueue: List<List<Request>>
get() = _requestQueue
@@ -58,29 +68,17 @@
override val graphState: StateFlow<GraphState>
get() = _graphState
- override fun startRepeating(request: Request) {
- repeatingRequest = request
- }
+ override fun submit(request: Request): Boolean = submit(listOf(request))
- override fun stopRepeating() {
- repeatingRequest = null
- }
-
- override fun hasRepeatingRequest() = repeatingRequest != null
-
- override fun submit(request: Request) {
- submit(listOf(request))
- }
-
- override fun submit(requests: List<Request>) {
+ override fun submit(requests: List<Request>): Boolean {
+ if (closed) return false
_requestQueue.add(requests)
+ return true
}
- override suspend fun trySubmit(parameters: Map<*, Any?>): Boolean {
- if (closed) {
- return false
- }
- if (repeatingRequest == null) return false
+ override fun submit(parameters: Map<*, Any?>): Boolean {
+ check(repeatingRequest != null)
+ if (closed) return false
val currProcessor = processor
val currRepeatingRequest = repeatingRequest
@@ -88,22 +86,37 @@
requiredParameters.putAllMetadata(parameters)
graphState3A.writeTo(requiredParameters)
- return when {
- currProcessor == null || currRepeatingRequest == null -> false
- else ->
- currProcessor.submit(
- isRepeating = false,
- requests = listOf(currRepeatingRequest),
- defaultParameters = defaultParameters,
- requiredParameters = requiredParameters,
- listeners = defaultListeners
- )
+ if (currProcessor != null && currRepeatingRequest != null) {
+ currProcessor.submit(
+ isRepeating = false,
+ requests = listOf(currRepeatingRequest),
+ defaultParameters = defaultParameters,
+ requiredParameters = requiredParameters,
+ listeners = defaultListeners
+ )
}
+ return true
}
override fun abort() {
+ val requests = _requestQueue.toList()
_requestQueue.clear()
- // TODO: Invoke abort on the listeners in the queue.
+
+ for (burst in requests) {
+ for (request in burst) {
+ for (listener in defaultListeners) {
+ listener.onAborted(request)
+ }
+ }
+ }
+
+ for (burst in requests) {
+ for (request in burst) {
+ for (listener in request.listeners) {
+ listener.onAborted(request)
+ }
+ }
+ }
}
override fun close() {
@@ -113,6 +126,7 @@
closed = true
active = false
_requestQueue.clear()
+ graphListener3A.onGraphShutdown()
}
override fun onGraphStarting() {
@@ -123,11 +137,12 @@
_graphState.value = GraphStateStarted
val old = processor
processor = requestProcessor
- old?.close()
+ runBlocking { old?.shutdown() }
}
override fun onGraphStopping() {
_graphState.value = GraphStateStopping
+ graphListener3A.onGraphStopped()
}
override fun onGraphStopped(requestProcessor: GraphRequestProcessor?) {
@@ -136,7 +151,7 @@
val old = processor
if (requestProcessor === old) {
processor = null
- old.close()
+ runBlocking { old.shutdown() }
}
}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/UpdateCounting3AStateListener.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/UpdateCounting3AStateListener.kt
index 8c4b838..11779e4 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/UpdateCounting3AStateListener.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/UpdateCounting3AStateListener.kt
@@ -37,5 +37,9 @@
return listener.update(requestNumber, frameMetadata)
}
- override fun onRequestSequenceStopped() {}
+ override fun onStopRepeating() {}
+
+ override fun onGraphStopped() {}
+
+ override fun onGraphShutdown() {}
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
index 2f89dcf..630a50a 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
@@ -182,10 +182,10 @@
if (mDynamicRangeResolver.is10BitDynamicRangeSupported()) {
generate10BitSupportedCombinationList();
+ }
- if (isUltraHdrSupported()) {
- generateUltraHdrSupportedCombinationList();
- }
+ if (isUltraHdrSupported()) {
+ generateUltraHdrSupportedCombinationList();
}
mIsStreamUseCaseSupported = StreamUseCaseUtil.isStreamUseCaseSupported(mCharacteristics);
@@ -288,7 +288,12 @@
List<SurfaceCombination> supportedSurfaceCombinations = new ArrayList<>();
- if (featureSettings.getRequiredMaxBitDepth() == DynamicRange.BIT_DEPTH_8_BIT) {
+ if (featureSettings.isUltraHdrOn()) {
+ // For Ultra HDR output, only the default camera mode is currently supported.
+ if (featureSettings.getCameraMode() == CameraMode.DEFAULT) {
+ supportedSurfaceCombinations.addAll(mSurfaceCombinationsUltraHdr);
+ }
+ } else if (featureSettings.getRequiredMaxBitDepth() == DynamicRange.BIT_DEPTH_8_BIT) {
switch (featureSettings.getCameraMode()) {
case CameraMode.CONCURRENT_CAMERA:
supportedSurfaceCombinations = mConcurrentSurfaceCombinations;
@@ -305,11 +310,7 @@
} else if (featureSettings.getRequiredMaxBitDepth() == DynamicRange.BIT_DEPTH_10_BIT) {
// For 10-bit outputs, only the default camera mode is currently supported.
if (featureSettings.getCameraMode() == CameraMode.DEFAULT) {
- if (featureSettings.isUltraHdrOn()) {
- supportedSurfaceCombinations.addAll(mSurfaceCombinationsUltraHdr);
- } else {
- supportedSurfaceCombinations.addAll(mSurfaceCombinations10Bit);
- }
+ supportedSurfaceCombinations.addAll(mSurfaceCombinations10Bit);
}
}
@@ -865,6 +866,13 @@
boolean isPreviewStabilizationOn, boolean isUltraHdrOn) {
int requiredMaxBitDepth = getRequiredMaxBitDepth(resolvedDynamicRanges);
+ if (cameraMode != CameraMode.DEFAULT && isUltraHdrOn) {
+ throw new IllegalArgumentException(String.format("Camera device id is %s. Ultra HDR "
+ + "is not currently supported in %s camera mode.",
+ mCameraId,
+ CameraMode.toLabelString(cameraMode)));
+ }
+
if (cameraMode != CameraMode.DEFAULT
&& requiredMaxBitDepth == DynamicRange.BIT_DEPTH_10_BIT) {
throw new IllegalArgumentException(String.format("Camera device id is %s. 10 bit "
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatBaseImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatBaseImpl.java
index 3b97170..ec4cc78 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatBaseImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatBaseImpl.java
@@ -24,11 +24,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.camera.core.Logger;
import androidx.camera.core.impl.ImageFormatConstants;
class StreamConfigurationMapCompatBaseImpl
implements StreamConfigurationMapCompat.StreamConfigurationMapCompatImpl {
+ private static final String TAG = "StreamConfigurationMapCompatBaseImpl";
+
final StreamConfigurationMap mStreamConfigurationMap;
StreamConfigurationMapCompatBaseImpl(@NonNull StreamConfigurationMap map) {
@@ -38,7 +41,14 @@
@Nullable
@Override
public int[] getOutputFormats() {
- return mStreamConfigurationMap.getOutputFormats();
+ // b/361590210: try-catch to workaround the NullPointerException issue when using
+ // StreamConfigurationMap provided by Robolectric.
+ try {
+ return mStreamConfigurationMap.getOutputFormats();
+ } catch (NullPointerException e) {
+ Logger.e(TAG, "Failed to get output formats from StreamConfigurationMap", e);
+ return null;
+ }
}
@Nullable
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ZslDisablerQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ZslDisablerQuirk.java
index 28fe77d..2f1ae09 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ZslDisablerQuirk.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ZslDisablerQuirk.java
@@ -27,9 +27,9 @@
/**
* <p>QuirkSummary
- * Bug Id: 252818931, 261744070, 319913852
- * Description: On certain devices, the captured image has color issue for reprocessing. We
- * need to disable zero-shutter lag and return false for
+ * Bug Id: 252818931, 261744070, 319913852, 361328838
+ * Description: On certain devices, the captured image has color or zoom freezing issue for
+ * reprocessing. We need to disable zero-shutter lag and return false for
* {@link CameraInfo#isZslSupported()}.
* Device(s): Samsung Fold4, Samsung s22, Xiaomi Mi 8
*/
@@ -39,7 +39,9 @@
"SM-F936",
"SM-S901U",
"SM-S908U",
- "SM-S908U1"
+ "SM-S908U1",
+ "SM-F721U1",
+ "SM-S928U1"
);
private static final List<String> AFFECTED_XIAOMI_MODEL = Arrays.asList(
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
index b4059b9..d665ba5 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
@@ -1739,6 +1739,7 @@
// Resolution selection tests for Ultra HDR
//
// //////////////////////////////////////////////////////////////////////////////////////////
+
@Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
fun checkUltraHdrCombinationsSupported() {
@@ -1768,6 +1769,33 @@
}
}
+ @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun checkUltraHdrCombinationsSupported_when8bit() {
+ // Device might support Ultra HDR but not 10-bit.
+ setupCameraAndInitCameraX(supportedFormats = intArrayOf(JPEG_R))
+ val supportedSurfaceCombination =
+ SupportedSurfaceCombination(
+ context,
+ DEFAULT_CAMERA_ID,
+ cameraManagerCompat!!,
+ mockCamcorderProfileHelper
+ )
+
+ GuaranteedConfigurationsUtil.getUltraHdrSupportedCombinationList().forEach {
+ assertThat(
+ supportedSurfaceCombination.checkSupported(
+ createFeatureSettings(
+ requiredMaxBitDepth = BIT_DEPTH_8_BIT,
+ isUltraHdrOn = true
+ ),
+ it.surfaceConfigList
+ )
+ )
+ .isTrue()
+ }
+ }
+
/** JPEG_R/MAXIMUM when Ultra HDR is ON. */
@Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
@@ -1775,7 +1803,6 @@
val jpegUseCase =
createUseCase(
CaptureType.IMAGE_CAPTURE,
- dynamicRange = HLG_10_BIT,
imageFormat = JPEG_R,
) // JPEG
val useCaseExpectedResultMap =
@@ -1796,7 +1823,27 @@
val jpegUseCase =
createUseCase(
CaptureType.IMAGE_CAPTURE,
- dynamicRange = HLG_10_BIT,
+ imageFormat = JPEG_R,
+ ) // JPEG
+ val useCaseExpectedResultMap =
+ mutableMapOf<UseCase, Size>().apply {
+ put(privUseCase, PREVIEW_SIZE)
+ put(jpegUseCase, MAXIMUM_SIZE)
+ }
+ getSuggestedSpecsAndVerify(
+ useCasesExpectedSizeMap = useCaseExpectedResultMap,
+ supportedOutputFormats = intArrayOf(JPEG_R),
+ )
+ }
+
+ /** HLG10 PRIV/PREVIEW + JPEG_R/MAXIMUM when Ultra HDR is ON. */
+ @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun canSelectCorrectSizes_hlg10PrivPlusJpegr_whenUltraHdrIsOn() {
+ val privUseCase = createUseCase(CaptureType.PREVIEW, dynamicRange = HLG_10_BIT) // PRIV
+ val jpegUseCase =
+ createUseCase(
+ CaptureType.IMAGE_CAPTURE,
imageFormat = JPEG_R,
) // JPEG
val useCaseExpectedResultMap =
@@ -1821,7 +1868,6 @@
val jpegUseCase =
createUseCase(
CaptureType.IMAGE_CAPTURE,
- dynamicRange = HLG_10_BIT,
imageFormat = JPEG_R,
) // JPEG
val useCaseExpectedResultMap =
@@ -3489,10 +3535,8 @@
set(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, hardwareLevel)
set(CameraCharacteristics.SENSOR_ORIENTATION, sensorOrientation)
set(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE, pixelArraySize)
- // Only setup stream configuration map when the supported output sizes are specified.
- supportedSizes?.let {
- set(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP, mockMap)
- }
+ // Setup stream configuration map, no matter supported output sizes are specified.
+ set(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP, mockMap)
set(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, deviceFPSRanges)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
dynamicRangeProfiles?.let {
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatTest.kt
index 29f150c..56f5d54 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatTest.kt
@@ -100,4 +100,22 @@
assertThat(streamConfigurationMapCompat.getHighResolutionOutputSizes(ImageFormat.JPEG))
.isNull()
}
+
+ @Test
+ fun getOutputFormats_notThrowingNullPointerException() {
+ val cameraId = "0"
+ val compat =
+ StreamConfigurationMapCompat.toStreamConfigurationMapCompat(
+ StreamConfigurationMapBuilder.newBuilder().build(),
+ OutputSizesCorrector(cameraId)
+ )
+
+ // b/361590210: check the workaround for NullPointerException issue (on API 23+) of
+ // StreamConfigurationMap provided by Robolectric is applied.
+ if (Build.VERSION.SDK_INT >= 23) {
+ assertThat(compat.getOutputFormats()).isNull()
+ } else {
+ assertThat(compat.getOutputFormats()).isNotNull()
+ }
+ }
}
diff --git a/camera/camera-compose/api/current.txt b/camera/camera-compose/api/current.txt
new file mode 100644
index 0000000..18c7f03
--- /dev/null
+++ b/camera/camera-compose/api/current.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.camera.compose {
+
+ public final class CameraXViewfinderKt {
+ method @androidx.compose.runtime.Composable public static void CameraXViewfinder(androidx.camera.core.SurfaceRequest surfaceRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.camera.viewfinder.surface.ImplementationMode implementationMode, optional androidx.camera.viewfinder.compose.MutableCoordinateTransformer? coordinateTransformer);
+ }
+
+}
+
diff --git a/activity/activity-compose/api/res-1.10.0-beta01.txt b/camera/camera-compose/api/res-current.txt
similarity index 100%
copy from activity/activity-compose/api/res-1.10.0-beta01.txt
copy to camera/camera-compose/api/res-current.txt
diff --git a/camera/camera-compose/api/restricted_current.txt b/camera/camera-compose/api/restricted_current.txt
new file mode 100644
index 0000000..18c7f03
--- /dev/null
+++ b/camera/camera-compose/api/restricted_current.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.camera.compose {
+
+ public final class CameraXViewfinderKt {
+ method @androidx.compose.runtime.Composable public static void CameraXViewfinder(androidx.camera.core.SurfaceRequest surfaceRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.camera.viewfinder.surface.ImplementationMode implementationMode, optional androidx.camera.viewfinder.compose.MutableCoordinateTransformer? coordinateTransformer);
+ }
+
+}
+
diff --git a/camera/camera-compose/build.gradle b/camera/camera-compose/build.gradle
new file mode 100644
index 0000000..337ad6c
--- /dev/null
+++ b/camera/camera-compose/build.gradle
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+/**
+ * This file was created using the `create_project.py` script located in the
+ * `<AndroidX root>/development/project-creator` directory.
+ *
+ * Please use that script when creating a new project, rather than copying an existing project and
+ * modifying its settings.
+ */
+import androidx.build.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("AndroidXComposePlugin")
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ api(project(":camera:camera-core"))
+ // TODO(b/357895362): Switch to pinned dependencies when stable is released
+ api(project(":camera:viewfinder:viewfinder-compose"))
+ api(project(":camera:viewfinder:viewfinder-core"))
+ implementation("androidx.compose.foundation:foundation-layout:1.6.1")
+ implementation("androidx.compose.foundation:foundation:1.6.1")
+ implementation("androidx.compose.runtime:runtime:1.6.1")
+ implementation("androidx.core:core-ktx:1.12.0")
+
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation(libs.testRules)
+ androidTestImplementation(libs.truth)
+ androidTestImplementation(project(":camera:camera-camera2"))
+ androidTestImplementation(project(":camera:camera-camera2-pipe-integration"))
+ androidTestImplementation(project(":camera:camera-lifecycle"))
+ androidTestImplementation(project(":camera:camera-testing")) {
+ // Ensure camera-testing does not pull in androidx.test dependencies
+ exclude(group:"androidx.test")
+ }
+ androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.6.1")
+ androidTestImplementation("androidx.compose.ui:ui-test-manifest:1.6.1")
+}
+
+android {
+ compileSdk 35
+ namespace "androidx.camera.compose"
+ // TODO(b/349411310): Remove once we can update runtime to 1.7.0
+ experimentalProperties["android.lint.useK2Uast"] = false
+}
+
+androidx {
+ name = "Camera Compose"
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
+ inceptionYear = "2024"
+ description = "Jetpack Compose tools for users of the Jetpack Camera camera-core library"
+}
diff --git a/camera/camera-compose/samples/build.gradle b/camera/camera-compose/samples/build.gradle
new file mode 100644
index 0000000..a2d5268
--- /dev/null
+++ b/camera/camera-compose/samples/build.gradle
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+/**
+ * This file was created using the `create_project.py` script located in the
+ * `<AndroidX root>/development/project-creator` directory.
+ *
+ * Please use that script when creating a new project, rather than copying an existing project and
+ * modifying its settings.
+ */
+import androidx.build.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("AndroidXComposePlugin")
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ api("androidx.annotation:annotation:1.8.1")
+ implementation(libs.kotlinStdlib)
+ implementation(project(":camera:camera-compose"))
+ implementation(project(":camera:camera-core"))
+ implementation("androidx.compose.foundation:foundation:1.6.8")
+ implementation("androidx.compose.runtime:runtime:1.6.8")
+ implementation("androidx.compose.ui:ui:1.6.8")
+ implementation("androidx.lifecycle:lifecycle-viewmodel:2.8.4")
+ compileOnly(project(":annotation:annotation-sampled"))
+}
+
+android {
+ compileSdk 35
+ namespace "androidx.camera.compose.samples"
+}
+
+androidx {
+ name = "Camera Compose Samples"
+ type = LibraryType.SAMPLES
+ inceptionYear = "2024"
+ description = "Contains sample code for the Androidx Camera Compose library"
+}
diff --git a/camera/camera-compose/samples/src/main/java/androidx/camera/compose/samples/CameraXViewfinderSamples.kt b/camera/camera-compose/samples/src/main/java/androidx/camera/compose/samples/CameraXViewfinderSamples.kt
new file mode 100644
index 0000000..82d2ba0
--- /dev/null
+++ b/camera/camera-compose/samples/src/main/java/androidx/camera/compose/samples/CameraXViewfinderSamples.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.compose.samples
+
+import android.util.Size
+import androidx.annotation.Sampled
+import androidx.camera.compose.CameraXViewfinder
+import androidx.camera.core.Preview
+import androidx.camera.core.SurfaceRequest
+import androidx.camera.viewfinder.compose.MutableCoordinateTransformer
+import androidx.camera.viewfinder.surface.ImplementationMode
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@Suppress("unused", "UNUSED_PARAMETER")
+@Sampled
+fun CameraXViewfinderSample() {
+ class PreviewViewModel : ViewModel() {
+ private val _surfaceRequests = MutableStateFlow<SurfaceRequest?>(null)
+
+ val surfaceRequests: StateFlow<SurfaceRequest?>
+ get() = _surfaceRequests.asStateFlow()
+
+ private fun produceSurfaceRequests(previewUseCase: Preview) {
+ // Always publish new SurfaceRequests from Preview
+ previewUseCase.setSurfaceProvider { newSurfaceRequest ->
+ _surfaceRequests.value = newSurfaceRequest
+ }
+ }
+
+ fun focusOnPoint(surfaceBounds: Size, x: Float, y: Float) {
+ // Create point for CameraX's CameraControl.startFocusAndMetering() and submit...
+ }
+
+ // ...
+ }
+
+ @Composable
+ fun MyCameraViewfinder(viewModel: PreviewViewModel, modifier: Modifier = Modifier) {
+ val currentSurfaceRequest: SurfaceRequest? by viewModel.surfaceRequests.collectAsState()
+
+ currentSurfaceRequest?.let { surfaceRequest ->
+
+ // CoordinateTransformer for transforming from Offsets to Surface coordinates
+ val coordinateTransformer = remember { MutableCoordinateTransformer() }
+
+ CameraXViewfinder(
+ surfaceRequest = surfaceRequest,
+ implementationMode = ImplementationMode.EXTERNAL, // Can also use EMBEDDED
+ modifier =
+ modifier.pointerInput(Unit) {
+ detectTapGestures {
+ with(coordinateTransformer) {
+ val surfaceCoords = it.transform()
+ viewModel.focusOnPoint(
+ surfaceRequest.resolution,
+ surfaceCoords.x,
+ surfaceCoords.y
+ )
+ }
+ }
+ },
+ coordinateTransformer = coordinateTransformer
+ )
+ }
+ }
+}
diff --git a/camera/camera-compose/src/androidTest/java/androidx.camera.compose/CameraXViewfinderTest.kt b/camera/camera-compose/src/androidTest/java/androidx.camera.compose/CameraXViewfinderTest.kt
new file mode 100644
index 0000000..3c53f75
--- /dev/null
+++ b/camera/camera-compose/src/androidTest/java/androidx.camera.compose/CameraXViewfinderTest.kt
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.compose
+
+import android.content.Context
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.pipe.integration.CameraPipeConfig
+import androidx.camera.core.Camera
+import androidx.camera.core.CameraXConfig
+import androidx.camera.core.Preview
+import androidx.camera.core.SurfaceRequest
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.impl.CameraPipeConfigTestRule
+import androidx.camera.testing.impl.CameraUtil
+import androidx.camera.testing.impl.CameraUtil.PreTestCameraIdList
+import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
+import androidx.camera.viewfinder.surface.ImplementationMode
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.concurrent.futures.await
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.resume
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.produceIn
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeout
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class CameraXViewfinderTest(private val implName: String, private val cameraConfig: CameraXConfig) {
+ @get:Rule
+ val cameraPipeConfigTestRule =
+ CameraPipeConfigTestRule(
+ active = implName == CameraPipeConfig::class.simpleName,
+ )
+
+ @get:Rule
+ val useCamera =
+ CameraUtil.grantCameraPermissionAndPreTestAndPostTest(PreTestCameraIdList(cameraConfig))
+
+ @get:Rule val composeTest = createComposeRule()
+
+ @Test
+ fun viewfinderIsDisplayed_withValidSurfaceRequest() = runViewfinderTest {
+ composeTest.setContent {
+ val currentSurfaceRequest: SurfaceRequest? by surfaceRequests.collectAsState()
+ currentSurfaceRequest?.let { surfaceRequest ->
+ CameraXViewfinder(
+ surfaceRequest = surfaceRequest,
+ modifier = Modifier.testTag(CAMERAX_VIEWFINDER_TEST_TAG)
+ )
+ }
+ }
+
+ // Start the camera
+ startCamera()
+
+ // Wait for first SurfaceRequest
+ surfaceRequests.filterNotNull().first()
+
+ composeTest.awaitIdle()
+
+ // CameraXViewfinder should now have a child Viewfinder
+ composeTest
+ .onNodeWithTag(CAMERAX_VIEWFINDER_TEST_TAG)
+ .assertIsDisplayed()
+ .assert(SemanticsMatcher.hasChild())
+ }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ @Test
+ fun changingImplementation_sendsNewSurfaceRequest() = runViewfinderTest {
+ var implementationMode: ImplementationMode by mutableStateOf(ImplementationMode.EXTERNAL)
+ composeTest.setContent {
+ val currentSurfaceRequest: SurfaceRequest? by surfaceRequests.collectAsState()
+ currentSurfaceRequest?.let { surfaceRequest ->
+ CameraXViewfinder(
+ surfaceRequest = surfaceRequest,
+ implementationMode = implementationMode,
+ modifier = Modifier.testTag(CAMERAX_VIEWFINDER_TEST_TAG)
+ )
+ }
+ }
+
+ // Collect expected number of SurfaceRequests for 2 mode changes
+ val surfaceRequestSequence = surfaceRequests.filterNotNull().take(3).produceIn(this)
+
+ // Start the camera
+ startCamera()
+
+ // Swap implementation modes twice to produce 3 SurfaceRequests
+ val allSurfaceRequests = buildList {
+ for (surfaceRequest in surfaceRequestSequence) {
+ add(surfaceRequest)
+ composeTest.awaitIdle()
+
+ if (!surfaceRequestSequence.isClosedForReceive) {
+ // Changing the implementation mode will invalidate the previous SurfaceRequest
+ // and cause Preview to send a new SurfaceRequest
+ implementationMode = implementationMode.swapMode()
+ composeTest.awaitIdle()
+ }
+ }
+ }
+
+ assertThat(allSurfaceRequests.size).isEqualTo(3)
+ assertThat(allSurfaceRequests).containsNoDuplicates()
+ }
+
+ @Test
+ fun cancelledSurfaceRequest_doesNotInstantiateViewfinder() = runViewfinderTest {
+ // Start the camera
+ startCamera()
+
+ // Wait for first SurfaceRequest
+ val surfaceRequest = surfaceRequests.filterNotNull().first()
+
+ // Reset surface provider to cause cancellation of the last SurfaceRequest
+ resetPreviewSurfaceProvider()
+
+ // Ensure the SurfaceRequest is cancelled
+ surfaceRequest.awaitCancellation()
+
+ // Pass on cancelled SurfaceRequest to CameraXViewfinder
+ composeTest.setContent {
+ CameraXViewfinder(
+ surfaceRequest = surfaceRequest,
+ modifier = Modifier.testTag(CAMERAX_VIEWFINDER_TEST_TAG)
+ )
+ }
+
+ composeTest.awaitIdle()
+
+ // Viewfinder should not be displayed since SurfaceRequest was cancelled
+ composeTest.onNodeWithTag(CAMERAX_VIEWFINDER_TEST_TAG).assertIsNotDisplayed()
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() =
+ listOf(
+ arrayOf(Camera2Config::class.simpleName, Camera2Config.defaultConfig()),
+ arrayOf(CameraPipeConfig::class.simpleName, CameraPipeConfig.defaultConfig())
+ )
+
+ private const val CAMERAX_VIEWFINDER_TEST_TAG = "CameraXViewfinderTestTag"
+ }
+
+ private inline fun runViewfinderTest(crossinline block: suspend PreviewTestScope.() -> Unit) =
+ runBlocking {
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ val cameraProvider =
+ withTimeout(10.seconds) {
+ ProcessCameraProvider.configureInstance(cameraConfig)
+ ProcessCameraProvider.getInstance(context).await()
+ }
+
+ var fakeLifecycleOwner: FakeLifecycleOwner? = null
+ try {
+ val preview = Preview.Builder().build()
+ val surfaceRequests = MutableStateFlow<SurfaceRequest?>(null)
+ val resetPreviewSurfaceProvider =
+ suspend {
+ withContext(Dispatchers.Main) {
+ // Reset the surface provider to a new lambda that will continue to
+ // publish to surfaceRequests
+ preview.setSurfaceProvider { surfaceRequest ->
+ surfaceRequests.value = surfaceRequest
+ }
+ }
+ }
+ .also { it.invoke() }
+
+ val startCamera = suspend {
+ withContext(Dispatchers.Main) {
+ val lifecycleOwner =
+ FakeLifecycleOwner().apply {
+ startAndResume()
+ fakeLifecycleOwner = this
+ }
+
+ val firstAvailableCameraSelector =
+ cameraProvider.availableCameraInfos
+ .asSequence()
+ .map { it.cameraSelector }
+ .first()
+ cameraProvider.bindToLifecycle(
+ lifecycleOwner,
+ firstAvailableCameraSelector,
+ preview
+ )
+ }
+ }
+
+ with(
+ PreviewTestScope(
+ surfaceRequests = surfaceRequests.asStateFlow(),
+ resetPreviewSurfaceProvider = resetPreviewSurfaceProvider,
+ startCamera = startCamera,
+ coroutineContext = coroutineContext
+ )
+ ) {
+ block()
+ }
+ } finally {
+ fakeLifecycleOwner?.apply {
+ withContext(Dispatchers.Main) {
+ pauseAndStop()
+ destroy()
+ }
+ }
+ withTimeout(30.seconds) { cameraProvider.shutdownAsync().await() }
+ }
+ }
+
+ private data class PreviewTestScope(
+ val surfaceRequests: StateFlow<SurfaceRequest?>,
+ val resetPreviewSurfaceProvider: suspend () -> Unit,
+ val startCamera: suspend () -> Camera,
+ override val coroutineContext: CoroutineContext
+ ) : CoroutineScope
+}
+
+private fun ImplementationMode.swapMode(): ImplementationMode {
+ return when (this) {
+ ImplementationMode.EXTERNAL -> ImplementationMode.EMBEDDED
+ ImplementationMode.EMBEDDED -> ImplementationMode.EXTERNAL
+ }
+}
+
+private fun SemanticsMatcher.Companion.hasChild() =
+ SemanticsMatcher("Has child") { node -> node.children.isNotEmpty() }
+
+private suspend fun SurfaceRequest.awaitCancellation(): Unit = suspendCancellableCoroutine { cont ->
+ addRequestCancellationListener(Runnable::run) { cont.resume(Unit) }
+}
diff --git a/camera/camera-compose/src/main/java/androidx/camera/androidx-camera-camera-compose-documentation.md b/camera/camera-compose/src/main/java/androidx/camera/androidx-camera-camera-compose-documentation.md
new file mode 100644
index 0000000..bc5cbbc
--- /dev/null
+++ b/camera/camera-compose/src/main/java/androidx/camera/androidx-camera-camera-compose-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+Camera Compose
+
+# Package androidx.camera.compose
+
+Jetpack Compose tools for users of the Jetpack Camera camera-core library
diff --git a/camera/camera-compose/src/main/java/androidx/camera/compose/CameraXViewfinder.kt b/camera/camera-compose/src/main/java/androidx/camera/compose/CameraXViewfinder.kt
new file mode 100644
index 0000000..cfc1f06
--- /dev/null
+++ b/camera/camera-compose/src/main/java/androidx/camera/compose/CameraXViewfinder.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.compose
+
+import androidx.camera.core.SurfaceRequest
+import androidx.camera.core.SurfaceRequest.TransformationInfo as CXTransformationInfo
+import androidx.camera.viewfinder.compose.MutableCoordinateTransformer
+import androidx.camera.viewfinder.compose.Viewfinder
+import androidx.camera.viewfinder.surface.ImplementationMode
+import androidx.camera.viewfinder.surface.TransformationInfo
+import androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.launch
+
+/**
+ * An adapter composable that displays frames from CameraX by completing provided [SurfaceRequest]s.
+ *
+ * This is a wrapper around [Viewfinder] that will convert a CameraX [SurfaceRequest] internally
+ * into a [ViewfinderSurfaceRequest]. Additionally, all interactions normally handled through the
+ * [ViewfinderSurfaceRequest] will be derived from the [SurfaceRequest].
+ *
+ * If [implementationMode] is changed while the provided [surfaceRequest] has been fulfilled, the
+ * surface request will be invalidated as if [SurfaceRequest.invalidate] has been called. This will
+ * allow CameraX to know that a new surface request is required since the underlying viewfinder
+ * implementation will be providing a new surface.
+ *
+ * Example usage:
+ *
+ * @sample androidx.camera.compose.samples.CameraXViewfinderSample
+ * @param surfaceRequest The surface request from CameraX
+ * @param modifier The [Modifier] to be applied to this viewfinder
+ * @param implementationMode The [ImplementationMode] to be used by this viewfinder.
+ * @param coordinateTransformer The [MutableCoordinateTransformer] used to map offsets of this
+ * viewfinder to the source coordinates of the data being provided to the surface that fulfills
+ * [surfaceRequest]
+ */
+@Composable
+public fun CameraXViewfinder(
+ surfaceRequest: SurfaceRequest,
+ modifier: Modifier = Modifier,
+ implementationMode: ImplementationMode = ImplementationMode.EXTERNAL,
+ coordinateTransformer: MutableCoordinateTransformer? = null
+) {
+ val currentImplementationMode by rememberUpdatedState(implementationMode)
+
+ val viewfinderArgs by
+ produceState<ViewfinderArgs?>(initialValue = null, surfaceRequest) {
+ // Convert the CameraX SurfaceRequest to ViewfinderSurfaceRequest. There should
+ // always be a 1:1 mapping of CameraX SurfaceRequest to ViewfinderSurfaceRequest.
+ val viewfinderSurfaceRequest =
+ ViewfinderSurfaceRequest.Builder(surfaceRequest.resolution).build()
+
+ // Launch undispatched so we always reach the try/finally in this coroutine
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ // Forward request cancellation to the ViewfinderSurfaceRequest by marking it
+ // safe to release and cancelling this produceScope in case we haven't yet
+ // produced a complete ViewfinderArgs.
+ surfaceRequest.addRequestCancellationListener(Runnable::run) {
+ // This SurfaceRequest doesn't need to be completed, so let the
+ // Viewfinder know in case it has already generated a Surface.
+ viewfinderSurfaceRequest.markSurfaceSafeToRelease()
+ // Also complete the ViewfinderSurfaceRequest from the producer side
+ // in case we never sent it to the Viewfinder.
+ viewfinderSurfaceRequest.willNotProvideSurface()
+ [email protected]()
+ }
+
+ // Suspend until we retrieve the Surface
+ val surface = viewfinderSurfaceRequest.getSurface()
+ // Provide the surface and mark safe to release once the
+ // frame producer is finished.
+ surfaceRequest.provideSurface(surface, Runnable::run) {
+ viewfinderSurfaceRequest.markSurfaceSafeToRelease()
+ }
+ } finally {
+ // If we haven't provided the surface, such as if we're cancelled
+ // while suspending on getSurface(), this call will succeed. Otherwise
+ // it will be a no-op.
+ surfaceRequest.willNotProvideSurface()
+ }
+ }
+
+ // Convert the CameraX TransformationInfo callback into a StateFlow
+ val transformationInfoFlow: StateFlow<CXTransformationInfo?> =
+ MutableStateFlow<CXTransformationInfo?>(null)
+ .also { stateFlow ->
+ // Set a callback to update this state flow
+ surfaceRequest.setTransformationInfoListener(Runnable::run) { transformInfo
+ ->
+ // Set the next value of the flow
+ stateFlow.value = transformInfo
+ }
+ }
+ .asStateFlow()
+
+ // The ImplementationMode that will be used for all TransformationInfo updates.
+ // This is locked in once we have updated ViewfinderArgs and won't change until
+ // this produceState block is cancelled and restarted.
+ var snapshotImplementationMode: ImplementationMode? = null
+ snapshotFlow { currentImplementationMode }
+ .combine(transformationInfoFlow.filterNotNull()) { implMode, transformInfo ->
+ Pair(implMode, transformInfo)
+ }
+ .takeWhile { (implMode, _) ->
+ val shouldAbort =
+ snapshotImplementationMode != null && implMode != snapshotImplementationMode
+ if (shouldAbort) {
+ // Abort flow and invalidate SurfaceRequest so a new SurfaceRequest will
+ // be sent.
+ surfaceRequest.invalidate()
+ } else {
+ // Got the first ImplementationMode. This will be used until this
+ // produceState is cancelled.
+ snapshotImplementationMode = implMode
+ }
+ !shouldAbort
+ }
+ .collect { (implMode, transformInfo) ->
+ value =
+ ViewfinderArgs(
+ viewfinderSurfaceRequest,
+ implMode,
+ TransformationInfo(
+ sourceRotation = transformInfo.rotationDegrees,
+ isSourceMirroredHorizontally = transformInfo.isMirroring,
+ isSourceMirroredVertically = false,
+ cropRectLeft = transformInfo.cropRect.left,
+ cropRectTop = transformInfo.cropRect.top,
+ cropRectRight = transformInfo.cropRect.right,
+ cropRectBottom = transformInfo.cropRect.bottom
+ )
+ )
+ }
+ }
+
+ viewfinderArgs?.let { args ->
+ Viewfinder(
+ surfaceRequest = args.viewfinderSurfaceRequest,
+ implementationMode = args.implementationMode,
+ transformationInfo = args.transformationInfo,
+ modifier = modifier.fillMaxSize(),
+ coordinateTransformer = coordinateTransformer
+ )
+ }
+}
+
+@Immutable
+private data class ViewfinderArgs(
+ val viewfinderSurfaceRequest: ViewfinderSurfaceRequest,
+ val implementationMode: ImplementationMode,
+ val transformationInfo: TransformationInfo
+)
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.kt b/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.kt
index 94fa6ae..a0557a5 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.kt
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.kt
@@ -18,6 +18,7 @@
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope
import androidx.lifecycle.LifecycleOwner
+import com.google.common.util.concurrent.ListenableFuture
/**
* A [CameraProvider] provides basic access to a set of cameras such as querying for camera
@@ -91,4 +92,12 @@
public fun getCameraInfo(cameraSelector: CameraSelector): CameraInfo {
throw UnsupportedOperationException("The camera provider is not implemented properly.")
}
+
+ /**
+ * Shuts down the camera provider.
+ *
+ * @return A [ListenableFuture] representing the shutdown status. Cancellation of this future is
+ * a no-op.
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP) public fun shutdownAsync(): ListenableFuture<Void>
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index 5c15ced..20b2ecd 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -19,7 +19,6 @@
import static android.graphics.ImageFormat.JPEG_R;
import static androidx.camera.core.CameraEffect.IMAGE_CAPTURE;
-import static androidx.camera.core.DynamicRange.HDR_UNSPECIFIED_10_BIT;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_BUFFER_FORMAT;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_CAPTURE_CONFIG_UNPACKER;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_DEFAULT_CAPTURE_CONFIG;
@@ -472,7 +471,7 @@
if (isOutputFormatUltraHdr(builder.getMutableConfig())) {
builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT, JPEG_R);
builder.getMutableConfig().insertOption(OPTION_INPUT_DYNAMIC_RANGE,
- HDR_UNSPECIFIED_10_BIT);
+ DynamicRange.UNSPECIFIED);
} else if (useSoftwareJpeg) {
builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT,
ImageFormat.YUV_420_888);
@@ -2323,7 +2322,7 @@
if (isOutputFormatUltraHdr(getMutableConfig())) {
getMutableConfig().insertOption(OPTION_INPUT_FORMAT, JPEG_R);
getMutableConfig().insertOption(OPTION_INPUT_DYNAMIC_RANGE,
- HDR_UNSPECIFIED_10_BIT);
+ DynamicRange.UNSPECIFIED);
} else {
getMutableConfig().insertOption(OPTION_INPUT_FORMAT, ImageFormat.JPEG);
}
@@ -2829,14 +2828,6 @@
*
* <p>If not set, the output format will default to {@link #OUTPUT_FORMAT_JPEG}.
*
- * <p>If an Ultra HDR output format is used, a {@link DynamicRange#HDR_UNSPECIFIED_10_BIT}
- * will be used as the dynamic range of this use case. Please note that some devices may not
- * be able to support configuring both SDR and HDR use cases at the same time, e.g. use
- * Ultra HDR ImageCapture with a SDR Preview. Configuring concurrent SDR and HDR on these
- * devices will result in an {@link IllegalArgumentException} to be thrown when invoking
- * {@code bindToLifecycle()}. Such device specific constraints can be queried by calling
- * {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints(long)}.
- *
* @param outputFormat The output image format. Value is {@link #OUTPUT_FORMAT_JPEG} or
* {@link #OUTPUT_FORMAT_JPEG_ULTRA_HDR}.
* @return The current Builder.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index 541a169..dc922ee 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -1031,9 +1031,16 @@
// TODO(b/309900490): since there are other places (e.g. SupportedSurfaceCombination in
// camera2) that feature combination constraints are enforced, it would be nice if they
// followed a similar pattern for checking constraints.
- if (hasExtension() && hasNonSdrConfig(useCases)) {
- throw new IllegalArgumentException("Extensions are only supported for use with "
- + "standard dynamic range.");
+ if (hasExtension()) {
+ if (hasNonSdrConfig(useCases)) {
+ throw new IllegalArgumentException("Extensions are only supported for use with "
+ + "standard dynamic range.");
+ }
+
+ if (hasUltraHdrImageCapture(useCases)) {
+ throw new IllegalArgumentException("Extensions are not supported for use with "
+ + "Ultra HDR image capture.");
+ }
}
// TODO(b/322311893): throw exception to block feature combination of effect with Ultra
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
index 16d4260..c35e89e 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
@@ -105,7 +105,6 @@
}
@Override
- @NonNull
public boolean isCaptureProcessProgressAvailable() {
throw new RuntimeException("Stub, replace with implementation.");
}
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
index 0688972..2f44ee0 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
@@ -735,14 +735,6 @@
}
@Test
- fun cannotConfigureTwice() {
- ProcessCameraProvider.configureInstance(FakeAppConfig.create())
- assertThrows<IllegalStateException> {
- ProcessCameraProvider.configureInstance(FakeAppConfig.create())
- }
- }
-
- @Test
fun shutdown_clearsPreviousConfiguration() {
ProcessCameraProvider.configureInstance(FakeAppConfig.create())
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProvider.kt b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProvider.kt
index 33cc057..08c1e72 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProvider.kt
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProvider.kt
@@ -15,11 +15,15 @@
*/
package androidx.camera.lifecycle
+import android.content.Context
import android.content.pm.PackageManager
+import androidx.annotation.RestrictTo
+import androidx.annotation.RestrictTo.Scope
import androidx.camera.core.Camera
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraProvider
import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraXConfig
import androidx.camera.core.CompositionSettings
import androidx.camera.core.ConcurrentCamera
import androidx.camera.core.ConcurrentCamera.SingleCameraConfig
@@ -28,21 +32,27 @@
import androidx.camera.core.Preview
import androidx.camera.core.UseCase
import androidx.camera.core.UseCaseGroup
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.core.impl.utils.futures.Futures
+import androidx.concurrent.futures.await
+import androidx.core.util.Preconditions
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
+import com.google.common.util.concurrent.ListenableFuture
/**
- * Provides access to a camera which has has its opening and closing controlled by a
- * [LifecycleOwner].
+ * Provides access to a camera which has its opening and closing controlled by a [LifecycleOwner].
*/
-internal interface LifecycleCameraProvider : CameraProvider {
+// TODO: Remove the annotation when LifecycleCameraProvider is ready to be public.
+@RestrictTo(Scope.LIBRARY_GROUP)
+public interface LifecycleCameraProvider : CameraProvider {
/**
* Returns `true` if the [UseCase] is bound to a lifecycle. Otherwise returns `false`.
*
* After binding a use case, use cases remain bound until the lifecycle reaches a
* [Lifecycle.State.DESTROYED] state or if is unbound by calls to [unbind] or [unbindAll].
*/
- fun isBound(useCase: UseCase): Boolean
+ public fun isBound(useCase: UseCase): Boolean
/**
* Unbinds all specified use cases from the lifecycle provider.
@@ -57,8 +67,9 @@
*
* @param useCases The collection of use cases to remove.
* @throws IllegalStateException If not called on main thread.
+ * @throws UnsupportedOperationException If called in concurrent mode.
*/
- fun unbind(vararg useCases: UseCase?)
+ public fun unbind(vararg useCases: UseCase?): Unit
/**
* Unbinds all use cases from the lifecycle provider and removes them from CameraX.
@@ -67,7 +78,7 @@
*
* @throws IllegalStateException If not called on main thread.
*/
- fun unbindAll()
+ public fun unbindAll(): Unit
/**
* Binds the collection of [UseCase] to a [LifecycleOwner].
@@ -124,7 +135,7 @@
* camera to be used for the given use cases.
* @throws UnsupportedOperationException If the camera is configured in concurrent mode.
*/
- fun bindToLifecycle(
+ public fun bindToLifecycle(
lifecycleOwner: LifecycleOwner,
cameraSelector: CameraSelector,
vararg useCases: UseCase?
@@ -142,7 +153,7 @@
*
* @throws UnsupportedOperationException If the camera is configured in concurrent mode.
*/
- fun bindToLifecycle(
+ public fun bindToLifecycle(
lifecycleOwner: LifecycleOwner,
cameraSelector: CameraSelector,
useCaseGroup: UseCaseGroup
@@ -169,8 +180,9 @@
*
* If the concurrent logical cameras are binding the same preview and video capture use cases,
* the concurrent cameras video recording will be supported. The concurrent camera preview
- * stream will be shared with video capture and record the concurrent cameras as a whole. The
- * [CompositionSettings] can be used to configure the position of each camera stream.
+ * stream will be shared with video capture and record the concurrent cameras streams as a
+ * composited stream. The [CompositionSettings] can be used to configure the position of each
+ * camera stream and different layouts can be built. See [CompositionSettings] for more details.
*
* If we want to open concurrent physical cameras, which are two front cameras or two back
* cameras, the device needs to support physical cameras and the capability could be checked via
@@ -206,5 +218,48 @@
* @see CameraInfo.isLogicalMultiCameraSupported
* @see CameraInfo.getPhysicalCameraInfos
*/
- fun bindToLifecycle(singleCameraConfigs: List<SingleCameraConfig?>): ConcurrentCamera
+ public fun bindToLifecycle(singleCameraConfigs: List<SingleCameraConfig?>): ConcurrentCamera
+
+ public companion object {
+ /**
+ * Creates a lifecycle camera provider instance.
+ *
+ * @param context The Application context.
+ * @param cameraXConfig The configuration options to configure the lifecycle camera
+ * provider. If not set, the default configuration will be used.
+ * @return The lifecycle camera provider instance.
+ */
+ @JvmOverloads
+ @JvmStatic
+ public suspend fun createInstance(
+ context: Context,
+ cameraXConfig: CameraXConfig? = null,
+ ): LifecycleCameraProvider {
+ return createInstanceAsync(context, cameraXConfig).await()
+ }
+
+ /**
+ * Creates a lifecycle camera provider instance asynchronously.
+ *
+ * @param context The Application context.
+ * @param cameraXConfig The configuration options to configure the lifecycle camera
+ * provider. If not set, the default configuration will be used.
+ * @return A [ListenableFuture] that will be completed when the lifecycle camera provider
+ * instance is initialized.
+ */
+ @JvmOverloads
+ @JvmStatic
+ public fun createInstanceAsync(
+ context: Context,
+ cameraXConfig: CameraXConfig? = null,
+ ): ListenableFuture<LifecycleCameraProvider> {
+ Preconditions.checkNotNull(context)
+ val lifecycleCameraProvider = LifecycleCameraProviderImpl()
+ return Futures.transform(
+ lifecycleCameraProvider.initAsync(context, cameraXConfig),
+ { lifecycleCameraProvider },
+ CameraXExecutors.directExecutor()
+ )
+ }
+ }
}
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProviderImpl.kt b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProviderImpl.kt
new file mode 100644
index 0000000..bb2d4f4
--- /dev/null
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProviderImpl.kt
@@ -0,0 +1,711 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.lifecycle
+
+import android.content.Context
+import android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT
+import androidx.annotation.GuardedBy
+import androidx.annotation.MainThread
+import androidx.camera.core.Camera
+import androidx.camera.core.CameraEffect
+import androidx.camera.core.CameraFilter
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.CameraInfoUnavailableException
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraX
+import androidx.camera.core.CameraXConfig
+import androidx.camera.core.CompositionSettings
+import androidx.camera.core.ConcurrentCamera
+import androidx.camera.core.ConcurrentCamera.SingleCameraConfig
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.Preview
+import androidx.camera.core.UseCase
+import androidx.camera.core.UseCaseGroup
+import androidx.camera.core.ViewPort
+import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT
+import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_SINGLE
+import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
+import androidx.camera.core.concurrent.CameraCoordinator.CameraOperatingMode
+import androidx.camera.core.impl.CameraConfig
+import androidx.camera.core.impl.CameraConfigs
+import androidx.camera.core.impl.CameraInternal
+import androidx.camera.core.impl.ExtendedCameraConfigProviderStore
+import androidx.camera.core.impl.RestrictedCameraInfo
+import androidx.camera.core.impl.UseCaseConfig
+import androidx.camera.core.impl.UseCaseConfigFactory.CaptureType
+import androidx.camera.core.impl.utils.ContextUtil
+import androidx.camera.core.impl.utils.Threads
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.core.impl.utils.futures.FutureCallback
+import androidx.camera.core.impl.utils.futures.FutureChain
+import androidx.camera.core.impl.utils.futures.Futures
+import androidx.camera.core.internal.CameraUseCaseAdapter
+import androidx.concurrent.futures.CallbackToFutureAdapter
+import androidx.core.util.Preconditions
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.tracing.trace
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.Objects
+import java.util.Objects.requireNonNull
+
+/** Implementation of the [LifecycleCameraProvider] interface. */
+internal class LifecycleCameraProviderImpl : LifecycleCameraProvider {
+ private val lock = Any()
+ @GuardedBy("mLock") private var cameraXConfigProvider: CameraXConfig.Provider? = null
+ @GuardedBy("mLock") private var cameraXInitializeFuture: ListenableFuture<Void>? = null
+ @GuardedBy("mLock") private var cameraXShutdownFuture = Futures.immediateFuture<Void>(null)
+ private val lifecycleCameraRepository = LifecycleCameraRepository()
+ private var cameraX: CameraX? = null
+ private var context: Context? = null
+ @GuardedBy("mLock")
+ private val cameraInfoMap: MutableMap<CameraUseCaseAdapter.CameraId, RestrictedCameraInfo> =
+ HashMap()
+
+ internal fun initAsync(
+ context: Context,
+ cameraXConfig: CameraXConfig? = null
+ ): ListenableFuture<Void> {
+ synchronized(lock) {
+ if (cameraXInitializeFuture != null) {
+ return cameraXInitializeFuture as ListenableFuture<Void>
+ }
+ cameraXConfig?.let { configure(it) }
+ val cameraX = CameraX(context, cameraXConfigProvider)
+
+ cameraXInitializeFuture =
+ CallbackToFutureAdapter.getFuture { completer ->
+ synchronized(lock) {
+ val future: ListenableFuture<Void> =
+ FutureChain.from(cameraXShutdownFuture)
+ .transformAsync(
+ { cameraX.initializeFuture },
+ CameraXExecutors.directExecutor()
+ )
+ Futures.addCallback(
+ future,
+ object : FutureCallback<Void?> {
+ override fun onSuccess(result: Void?) {
+ [email protected] = cameraX
+ [email protected] =
+ ContextUtil.getApplicationContext(context)
+ completer.set(null)
+ }
+
+ override fun onFailure(t: Throwable) {
+ completer.setException(t)
+ }
+ },
+ CameraXExecutors.directExecutor()
+ )
+ }
+
+ "LifecycleCameraProvider-initialize"
+ }
+
+ return cameraXInitializeFuture as ListenableFuture<Void>
+ }
+ }
+
+ /**
+ * Configures the camera provider.
+ *
+ * The camera provider can only be configured once. Trying to configure it multiple times will
+ * throw an [IllegalStateException].
+ *
+ * @param cameraXConfig The CameraX configuration.
+ */
+ internal fun configure(cameraXConfig: CameraXConfig) =
+ trace("CX:configureInstanceInternal") {
+ synchronized(lock) {
+ Preconditions.checkNotNull(cameraXConfig)
+ Preconditions.checkState(
+ cameraXConfigProvider == null,
+ "CameraX has already been configured. To use a different configuration, " +
+ "shutdown() must be called."
+ )
+ cameraXConfigProvider = CameraXConfig.Provider { cameraXConfig }
+ }
+ }
+
+ override fun shutdownAsync(): ListenableFuture<Void> {
+ Threads.runOnMainSync {
+ unbindAll()
+ lifecycleCameraRepository.clear()
+ }
+
+ if (cameraX != null) {
+ cameraX!!.cameraFactory.cameraCoordinator.shutdown()
+ }
+
+ val shutdownFuture =
+ if (cameraX != null) cameraX!!.shutdown() else Futures.immediateFuture<Void>(null)
+
+ synchronized(lock) {
+ cameraXConfigProvider = null
+ cameraXInitializeFuture = null
+ cameraXShutdownFuture = shutdownFuture
+ cameraInfoMap.clear()
+ }
+ cameraX = null
+ context = null
+ return shutdownFuture
+ }
+
+ override fun isBound(useCase: UseCase): Boolean {
+ for (lifecycleCamera: LifecycleCamera in lifecycleCameraRepository.lifecycleCameras) {
+ if (lifecycleCamera.isBound(useCase)) {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ @MainThread
+ override fun unbind(vararg useCases: UseCase?): Unit =
+ trace("CX:unbind") {
+ Threads.checkMainThread()
+
+ if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
+ throw UnsupportedOperationException(
+ "Unbind usecase is not supported in concurrent camera mode, call unbindAll() first."
+ )
+ }
+
+ lifecycleCameraRepository.unbind(listOf(*useCases))
+ }
+
+ @MainThread
+ override fun unbindAll(): Unit =
+ trace("CX:unbindAll") {
+ Threads.checkMainThread()
+ cameraOperatingMode = CAMERA_OPERATING_MODE_UNSPECIFIED
+ lifecycleCameraRepository.unbindAll()
+ }
+
+ @Throws(CameraInfoUnavailableException::class)
+ override fun hasCamera(cameraSelector: CameraSelector): Boolean =
+ trace("CX:hasCamera") {
+ try {
+ cameraSelector.select(cameraX!!.cameraRepository.cameras)
+ } catch (e: IllegalArgumentException) {
+ return@trace false
+ }
+
+ return@trace true
+ }
+
+ @MainThread
+ override fun bindToLifecycle(
+ lifecycleOwner: LifecycleOwner,
+ cameraSelector: CameraSelector,
+ vararg useCases: UseCase?
+ ): Camera =
+ trace("CX:bindToLifecycle") {
+ if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
+ throw UnsupportedOperationException(
+ "bindToLifecycle for single camera is not supported in concurrent camera mode, " +
+ "call unbindAll() first"
+ )
+ }
+ cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
+ val camera =
+ bindToLifecycle(
+ lifecycleOwner,
+ cameraSelector,
+ null,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ null,
+ emptyList<CameraEffect>(),
+ *useCases
+ )
+ return@trace camera
+ }
+
+ @MainThread
+ public override fun bindToLifecycle(
+ lifecycleOwner: LifecycleOwner,
+ cameraSelector: CameraSelector,
+ useCaseGroup: UseCaseGroup
+ ): Camera =
+ trace("CX:bindToLifecycle-UseCaseGroup") {
+ if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
+ throw UnsupportedOperationException(
+ "bindToLifecycle for single camera is not supported in concurrent camera mode, " +
+ "call unbindAll() first."
+ )
+ }
+ cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
+ val camera =
+ bindToLifecycle(
+ lifecycleOwner,
+ cameraSelector,
+ null,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ useCaseGroup.viewPort,
+ useCaseGroup.effects,
+ *useCaseGroup.useCases.toTypedArray<UseCase>()
+ )
+ return@trace camera
+ }
+
+ @MainThread
+ override fun bindToLifecycle(singleCameraConfigs: List<SingleCameraConfig?>): ConcurrentCamera =
+ trace("CX:bindToLifecycle-Concurrent") {
+ if (singleCameraConfigs.size < 2) {
+ throw IllegalArgumentException("Concurrent camera needs two camera configs.")
+ }
+
+ if (singleCameraConfigs.size > 2) {
+ throw IllegalArgumentException(
+ "Concurrent camera is only supporting two cameras at maximum."
+ )
+ }
+
+ val firstCameraConfig = singleCameraConfigs[0]!!
+ val secondCameraConfig = singleCameraConfigs[1]!!
+
+ val cameras: MutableList<Camera> = ArrayList()
+ if (
+ firstCameraConfig.cameraSelector.lensFacing ==
+ secondCameraConfig.cameraSelector.lensFacing
+ ) {
+ if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
+ throw UnsupportedOperationException(
+ "Camera is already running, call unbindAll() before binding more cameras."
+ )
+ }
+ if (
+ firstCameraConfig.lifecycleOwner != secondCameraConfig.lifecycleOwner ||
+ firstCameraConfig.useCaseGroup.viewPort !=
+ secondCameraConfig.useCaseGroup.viewPort ||
+ firstCameraConfig.useCaseGroup.effects !=
+ secondCameraConfig.useCaseGroup.effects
+ ) {
+ throw IllegalArgumentException(
+ "Two camera configs need to have the same lifecycle owner, view port and " +
+ "effects."
+ )
+ }
+ val lifecycleOwner = firstCameraConfig.lifecycleOwner
+ val cameraSelector = firstCameraConfig.cameraSelector
+ val viewPort = firstCameraConfig.useCaseGroup.viewPort
+ val effects = firstCameraConfig.useCaseGroup.effects
+ val useCases: MutableList<UseCase> = ArrayList()
+ for (config: SingleCameraConfig? in singleCameraConfigs) {
+ // Connect physical camera id with use case.
+ for (useCase: UseCase in config!!.useCaseGroup.useCases) {
+ config.cameraSelector.physicalCameraId?.let {
+ useCase.setPhysicalCameraId(it)
+ }
+ }
+ useCases.addAll(config.useCaseGroup.useCases)
+ }
+
+ cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
+ val camera =
+ bindToLifecycle(
+ lifecycleOwner,
+ cameraSelector,
+ null,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ viewPort,
+ effects,
+ *useCases.toTypedArray<UseCase>()
+ )
+ cameras.add(camera)
+ } else {
+ if (!context!!.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
+ throw UnsupportedOperationException(
+ "Concurrent camera is not supported on the device."
+ )
+ }
+
+ if (cameraOperatingMode == CAMERA_OPERATING_MODE_SINGLE) {
+ throw UnsupportedOperationException(
+ "Camera is already running, call unbindAll() before binding more cameras."
+ )
+ }
+
+ val cameraInfosToBind: MutableList<CameraInfo> = ArrayList()
+ val firstCameraInfo: CameraInfo
+ val secondCameraInfo: CameraInfo
+ try {
+ firstCameraInfo = getCameraInfo(firstCameraConfig.cameraSelector)
+ secondCameraInfo = getCameraInfo(secondCameraConfig.cameraSelector)
+ } catch (e: IllegalArgumentException) {
+ throw IllegalArgumentException("Invalid camera selectors in camera configs.")
+ }
+ cameraInfosToBind.add(firstCameraInfo)
+ cameraInfosToBind.add(secondCameraInfo)
+ if (
+ activeConcurrentCameraInfos.isNotEmpty() &&
+ cameraInfosToBind != activeConcurrentCameraInfos
+ ) {
+ throw UnsupportedOperationException(
+ "Cameras are already running, call unbindAll() before binding more cameras."
+ )
+ }
+
+ cameraOperatingMode = CAMERA_OPERATING_MODE_CONCURRENT
+
+ // For dual camera video capture, we are only supporting two use cases:
+ // Preview + VideoCapture. If ImageCapture support is added, the validation logic
+ // will be updated accordingly.
+ var isDualCameraVideoCapture = false
+ if (
+ Objects.equals(
+ firstCameraConfig.useCaseGroup.useCases,
+ secondCameraConfig.useCaseGroup.useCases
+ ) && firstCameraConfig.useCaseGroup.useCases.size == 2
+ ) {
+ val useCase0 = firstCameraConfig.useCaseGroup.useCases[0]
+ val useCase1 = firstCameraConfig.useCaseGroup.useCases[1]
+ isDualCameraVideoCapture =
+ (isVideoCapture(useCase0) && isPreview(useCase1)) ||
+ (isPreview(useCase0) && isVideoCapture(useCase1))
+ }
+
+ if (isDualCameraVideoCapture) {
+ cameras.add(
+ bindToLifecycle(
+ firstCameraConfig.lifecycleOwner,
+ firstCameraConfig.cameraSelector,
+ secondCameraConfig.cameraSelector,
+ firstCameraConfig.compositionSettings,
+ secondCameraConfig.compositionSettings,
+ firstCameraConfig.useCaseGroup.viewPort,
+ firstCameraConfig.useCaseGroup.effects,
+ *firstCameraConfig.useCaseGroup.useCases.toTypedArray<UseCase>(),
+ )
+ )
+ } else {
+ for (config: SingleCameraConfig? in singleCameraConfigs) {
+ val camera =
+ bindToLifecycle(
+ config!!.lifecycleOwner,
+ config.cameraSelector,
+ null,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ config.useCaseGroup.viewPort,
+ config.useCaseGroup.effects,
+ *config.useCaseGroup.useCases.toTypedArray<UseCase>()
+ )
+ cameras.add(camera)
+ }
+ }
+ activeConcurrentCameraInfos = cameraInfosToBind
+ }
+ return@trace ConcurrentCamera(cameras)
+ }
+
+ override val availableCameraInfos: List<CameraInfo>
+ get() =
+ trace("CX:getAvailableCameraInfos") {
+ val availableCameraInfos: MutableList<CameraInfo> = ArrayList()
+ val cameras: Set<CameraInternal> = cameraX!!.cameraRepository.cameras
+ for (camera: CameraInternal in cameras) {
+ availableCameraInfos.add(camera.cameraInfo)
+ }
+ return@trace availableCameraInfos
+ }
+
+ override val availableConcurrentCameraInfos: List<List<CameraInfo>>
+ get() =
+ trace("CX:getAvailableConcurrentCameraInfos") {
+ requireNonNull(cameraX)
+ requireNonNull(cameraX!!.cameraFactory.cameraCoordinator)
+ val concurrentCameraSelectorLists =
+ cameraX!!.cameraFactory.cameraCoordinator.concurrentCameraSelectors
+
+ val availableConcurrentCameraInfos: MutableList<List<CameraInfo>> = ArrayList()
+ for (cameraSelectors in concurrentCameraSelectorLists) {
+ val cameraInfos: MutableList<CameraInfo> = ArrayList()
+ for (cameraSelector in cameraSelectors) {
+ var cameraInfo: CameraInfo
+ try {
+ cameraInfo = getCameraInfo(cameraSelector)
+ } catch (e: IllegalArgumentException) {
+ continue
+ }
+ cameraInfos.add(cameraInfo)
+ }
+ availableConcurrentCameraInfos.add(cameraInfos)
+ }
+ return@trace availableConcurrentCameraInfos
+ }
+
+ override val isConcurrentCameraModeOn: Boolean
+ /**
+ * Returns whether there is a [ConcurrentCamera] bound.
+ *
+ * @return `true` if there is a [ConcurrentCamera] bound, otherwise `false`.
+ */
+ @MainThread get() = cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT
+
+ /**
+ * Binds [ViewPort] and a collection of [UseCase] to a [LifecycleOwner].
+ *
+ * The state of the lifecycle will determine when the cameras are open, started, stopped and
+ * closed. When started, the use cases receive camera data.
+ *
+ * Binding to a [LifecycleOwner] in state currently in [Lifecycle.State.STARTED] or greater will
+ * also initialize and start data capture. If the camera was already running this may cause a
+ * new initialization to occur temporarily stopping data from the camera before restarting it.
+ *
+ * Multiple use cases can be bound via adding them all to a single [bindToLifecycle] call, or by
+ * using multiple [bindToLifecycle] calls. Using a single call that includes all the use cases
+ * helps to set up a camera session correctly for all uses cases, such as by allowing
+ * determination of resolutions depending on all the use cases bound being bound. If the use
+ * cases are bound separately, it will find the supported resolution with the priority depending
+ * on the binding sequence. If the use cases are bound with a single call, it will find the
+ * supported resolution with the priority in sequence of [ImageCapture], [Preview] and then
+ * [ImageAnalysis]. The resolutions that can be supported depends on the camera device hardware
+ * level that there are some default guaranteed resolutions listed in
+ * [android.hardware.camera2.CameraDevice.createCaptureSession].
+ *
+ * Currently up to 3 use cases may be bound to a [Lifecycle] at any time. Exceeding capability
+ * of target camera device will throw an IllegalArgumentException.
+ *
+ * A [UseCase] should only be bound to a single lifecycle and camera selector a time. Attempting
+ * to bind a use case to a lifecycle when it is already bound to another lifecycle is an error,
+ * and the use case binding will not change. Attempting to bind the same use case to multiple
+ * camera selectors is also an error and will not change the binding.
+ *
+ * If different use cases are bound to different camera selectors that resolve to distinct
+ * cameras, but the same lifecycle, only one of the cameras will operate at a time. The
+ * non-operating camera will not become active until it is the only camera with use cases bound.
+ *
+ * The [Camera] returned is determined by the given camera selector, plus other internal
+ * requirements, possibly from use case configurations. The camera returned from
+ * [bindToLifecycle] may differ from the camera determined solely by a camera selector. If the
+ * camera selector can't resolve a camera under the requirements, an [IllegalArgumentException]
+ * will be thrown.
+ *
+ * Only [UseCase] bound to latest active [Lifecycle] can keep alive. [UseCase] bound to other
+ * [Lifecycle] will be stopped.
+ *
+ * @param lifecycleOwner The [LifecycleOwner] which controls the lifecycle transitions of the
+ * use cases.
+ * @param primaryCameraSelector The primary camera selector which determines the camera to use
+ * for set of use cases.
+ * @param secondaryCameraSelector The secondary camera selector in dual camera case.
+ * @param primaryCompositionSettings The composition settings for the primary camera.
+ * @param secondaryCompositionSettings The composition settings for the secondary camera.
+ * @param viewPort The viewPort which represents the visible camera sensor rect.
+ * @param effects The effects applied to the camera outputs.
+ * @param useCases The use cases to bind to a lifecycle.
+ * @return The [Camera] instance which is determined by the camera selector and internal
+ * requirements.
+ * @throws IllegalStateException If the use case has already been bound to another lifecycle or
+ * method is not called on main thread.
+ * @throws IllegalArgumentException If the provided camera selector is unable to resolve a
+ * camera to be used for the given use cases.
+ */
+ @Suppress("unused")
+ private fun bindToLifecycle(
+ lifecycleOwner: LifecycleOwner,
+ primaryCameraSelector: CameraSelector,
+ secondaryCameraSelector: CameraSelector?,
+ primaryCompositionSettings: CompositionSettings,
+ secondaryCompositionSettings: CompositionSettings,
+ viewPort: ViewPort?,
+ effects: List<CameraEffect?>,
+ vararg useCases: UseCase?
+ ): Camera =
+ trace("CX:bindToLifecycle-internal") {
+ Threads.checkMainThread()
+ // TODO(b/153096869): override UseCase's target rotation.
+
+ // Get the LifecycleCamera if existed.
+ val primaryCameraInternal =
+ primaryCameraSelector.select(cameraX!!.cameraRepository.cameras)
+ primaryCameraInternal.setPrimary(true)
+ val primaryRestrictedCameraInfo =
+ getCameraInfo(primaryCameraSelector) as RestrictedCameraInfo
+
+ var secondaryCameraInternal: CameraInternal? = null
+ var secondaryRestrictedCameraInfo: RestrictedCameraInfo? = null
+ if (secondaryCameraSelector != null) {
+ secondaryCameraInternal =
+ secondaryCameraSelector.select(cameraX!!.cameraRepository.cameras)
+ secondaryCameraInternal.setPrimary(false)
+ secondaryRestrictedCameraInfo =
+ getCameraInfo(secondaryCameraSelector) as RestrictedCameraInfo
+ }
+
+ var lifecycleCameraToBind =
+ lifecycleCameraRepository.getLifecycleCamera(
+ lifecycleOwner,
+ CameraUseCaseAdapter.generateCameraId(
+ primaryRestrictedCameraInfo,
+ secondaryRestrictedCameraInfo
+ )
+ )
+
+ // Check if there's another camera that has already been bound.
+ val lifecycleCameras = lifecycleCameraRepository.lifecycleCameras
+ useCases.filterNotNull().forEach { useCase ->
+ for (lifecycleCamera: LifecycleCamera in lifecycleCameras) {
+ if (
+ lifecycleCamera.isBound(useCase) && lifecycleCamera != lifecycleCameraToBind
+ ) {
+ throw IllegalStateException(
+ String.format(
+ "Use case %s already bound to a different lifecycle.",
+ useCase
+ )
+ )
+ }
+ }
+ }
+
+ // Create the LifecycleCamera if there's no existing one that can be used.
+ if (lifecycleCameraToBind == null) {
+ lifecycleCameraToBind =
+ lifecycleCameraRepository.createLifecycleCamera(
+ lifecycleOwner,
+ CameraUseCaseAdapter(
+ primaryCameraInternal,
+ secondaryCameraInternal,
+ primaryRestrictedCameraInfo,
+ secondaryRestrictedCameraInfo,
+ primaryCompositionSettings,
+ secondaryCompositionSettings,
+ cameraX!!.cameraFactory.cameraCoordinator,
+ cameraX!!.cameraDeviceSurfaceManager,
+ cameraX!!.defaultConfigFactory
+ )
+ )
+ }
+
+ if (useCases.isEmpty()) {
+ return@trace lifecycleCameraToBind!!
+ }
+
+ lifecycleCameraRepository.bindToLifecycleCamera(
+ lifecycleCameraToBind!!,
+ viewPort,
+ effects,
+ listOf(*useCases),
+ cameraX!!.cameraFactory.cameraCoordinator
+ )
+
+ return@trace lifecycleCameraToBind
+ }
+
+ override fun getCameraInfo(cameraSelector: CameraSelector): CameraInfo =
+ trace("CX:getCameraInfo") {
+ val cameraInfoInternal =
+ cameraSelector.select(cameraX!!.cameraRepository.cameras).cameraInfoInternal
+ val cameraConfig = getCameraConfig(cameraSelector, cameraInfoInternal)
+
+ val key =
+ CameraUseCaseAdapter.CameraId.create(
+ cameraInfoInternal.cameraId,
+ cameraConfig.compatibilityId
+ )
+ var restrictedCameraInfo: RestrictedCameraInfo?
+ synchronized(lock) {
+ restrictedCameraInfo = cameraInfoMap[key]
+ if (restrictedCameraInfo == null) {
+ restrictedCameraInfo = RestrictedCameraInfo(cameraInfoInternal, cameraConfig)
+ cameraInfoMap[key] = restrictedCameraInfo!!
+ }
+ }
+
+ return@trace restrictedCameraInfo!!
+ }
+
+ private fun isVideoCapture(useCase: UseCase): Boolean {
+ return useCase.currentConfig.containsOption(UseCaseConfig.OPTION_CAPTURE_TYPE) &&
+ useCase.currentConfig.captureType == CaptureType.VIDEO_CAPTURE
+ }
+
+ private fun isPreview(useCase: UseCase): Boolean {
+ return useCase is Preview
+ }
+
+ private fun getCameraConfig(
+ cameraSelector: CameraSelector,
+ cameraInfo: CameraInfo
+ ): CameraConfig {
+ var cameraConfig: CameraConfig? = null
+ for (cameraFilter: CameraFilter in cameraSelector.cameraFilterSet) {
+ if (cameraFilter.identifier != CameraFilter.DEFAULT_ID) {
+ val extendedCameraConfig =
+ ExtendedCameraConfigProviderStore.getConfigProvider(cameraFilter.identifier)
+ .getConfig(cameraInfo, (context)!!)
+ if (extendedCameraConfig == null) { // ignore IDs unrelated to camera configs.
+ continue
+ }
+
+ // Only allows one camera config now.
+ if (cameraConfig != null) {
+ throw IllegalArgumentException(
+ "Cannot apply multiple extended camera configs at the same time."
+ )
+ }
+ cameraConfig = extendedCameraConfig
+ }
+ }
+
+ if (cameraConfig == null) {
+ cameraConfig = CameraConfigs.defaultConfig()
+ }
+ return cameraConfig
+ }
+
+ @get:CameraOperatingMode
+ private var cameraOperatingMode: Int
+ get() {
+ if (cameraX == null) {
+ return CAMERA_OPERATING_MODE_UNSPECIFIED
+ }
+ return cameraX!!.cameraFactory.cameraCoordinator.cameraOperatingMode
+ }
+ private set(cameraOperatingMode) {
+ if (cameraX == null) {
+ return
+ }
+ cameraX!!.cameraFactory.cameraCoordinator.cameraOperatingMode = cameraOperatingMode
+ }
+
+ private var activeConcurrentCameraInfos: List<CameraInfo>
+ get() {
+ if (cameraX == null) {
+ return java.util.ArrayList()
+ }
+ return cameraX!!.cameraFactory.cameraCoordinator.activeConcurrentCameraInfos
+ }
+ private set(cameraInfos) {
+ if (cameraX == null) {
+ return
+ }
+ cameraX!!.cameraFactory.cameraCoordinator.activeConcurrentCameraInfos = cameraInfos
+ }
+
+ companion object {
+ private const val TAG = "LifecycleCameraProvider"
+ }
+}
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
index ca49e20..9814e90 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
@@ -18,55 +18,24 @@
import android.app.Application
import android.content.Context
-import android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT
-import androidx.annotation.GuardedBy
import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
import androidx.camera.core.Camera
-import androidx.camera.core.CameraEffect
-import androidx.camera.core.CameraFilter
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraInfoUnavailableException
import androidx.camera.core.CameraSelector
-import androidx.camera.core.CameraX
import androidx.camera.core.CameraXConfig
-import androidx.camera.core.CompositionSettings
import androidx.camera.core.ConcurrentCamera
-import androidx.camera.core.ConcurrentCamera.SingleCameraConfig
-import androidx.camera.core.ImageAnalysis
-import androidx.camera.core.ImageCapture
import androidx.camera.core.InitializationException
-import androidx.camera.core.Preview
import androidx.camera.core.UseCase
import androidx.camera.core.UseCaseGroup
-import androidx.camera.core.ViewPort
-import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT
-import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_SINGLE
-import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
-import androidx.camera.core.concurrent.CameraCoordinator.CameraOperatingMode
-import androidx.camera.core.impl.CameraConfig
-import androidx.camera.core.impl.CameraConfigs
-import androidx.camera.core.impl.CameraInternal
-import androidx.camera.core.impl.ExtendedCameraConfigProviderStore
-import androidx.camera.core.impl.RestrictedCameraInfo
-import androidx.camera.core.impl.UseCaseConfig
-import androidx.camera.core.impl.UseCaseConfigFactory.CaptureType
-import androidx.camera.core.impl.utils.ContextUtil
-import androidx.camera.core.impl.utils.Threads
import androidx.camera.core.impl.utils.executor.CameraXExecutors
-import androidx.camera.core.impl.utils.futures.FutureCallback
-import androidx.camera.core.impl.utils.futures.FutureChain
import androidx.camera.core.impl.utils.futures.Futures
-import androidx.camera.core.internal.CameraUseCaseAdapter
import androidx.camera.lifecycle.ProcessCameraProvider.Companion.getInstance
-import androidx.concurrent.futures.CallbackToFutureAdapter
import androidx.core.util.Preconditions
-import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.tracing.trace
import com.google.common.util.concurrent.ListenableFuture
-import java.util.Objects
-import java.util.Objects.requireNonNull
/**
* A singleton which can be used to bind the lifecycle of cameras to any [LifecycleOwner] within an
@@ -82,743 +51,85 @@
*
* This is the standard provider for applications to use.
*/
-public class ProcessCameraProvider private constructor() : LifecycleCameraProvider {
- private val mLock = Any()
+// TODO: Remove the annotation when LifecycleCameraProvider is ready to be public.
+@Suppress("HiddenSuperclass")
+public class ProcessCameraProvider
+private constructor(private val lifecycleCameraProvider: LifecycleCameraProviderImpl) :
+ LifecycleCameraProvider {
- @GuardedBy("mLock") private var mCameraXConfigProvider: CameraXConfig.Provider? = null
-
- @GuardedBy("mLock") private var mCameraXInitializeFuture: ListenableFuture<CameraX>? = null
-
- @GuardedBy("mLock") private var mCameraXShutdownFuture = Futures.immediateFuture<Void>(null)
-
- private val mLifecycleCameraRepository = LifecycleCameraRepository()
- private var mCameraX: CameraX? = null
- private var mContext: Context? = null
-
- @GuardedBy("mLock")
- private val mCameraInfoMap: MutableMap<CameraUseCaseAdapter.CameraId, RestrictedCameraInfo> =
- HashMap()
-
- /**
- * Allows shutting down this ProcessCameraProvider instance so a new instance can be retrieved
- * by [getInstance].
- *
- * Once shutdownAsync is invoked, a new instance can be retrieved with [getInstance].
- *
- * This method should be used for testing purposes only. Along with [configureInstance], this
- * allows the process camera provider to be used in test suites which may need to initialize
- * CameraX in different ways in between tests.
- *
- * @return A [ListenableFuture] representing the shutdown status. Cancellation of this future is
- * a no-op.
- */
- @VisibleForTesting
- public fun shutdownAsync(): ListenableFuture<Void> {
- Threads.runOnMainSync {
- unbindAll()
- mLifecycleCameraRepository.clear()
- }
-
- if (mCameraX != null) {
- mCameraX!!.cameraFactory.cameraCoordinator.shutdown()
- }
-
- val shutdownFuture =
- if (mCameraX != null) mCameraX!!.shutdown() else Futures.immediateFuture<Void>(null)
-
- synchronized(mLock) {
- mCameraXConfigProvider = null
- mCameraXInitializeFuture = null
- mCameraXShutdownFuture = shutdownFuture
- mCameraInfoMap.clear()
- }
- mCameraX = null
- mContext = null
- return shutdownFuture
+ override fun isBound(useCase: UseCase): Boolean {
+ return lifecycleCameraProvider.isBound(useCase)
}
@MainThread
- public override fun bindToLifecycle(
+ override fun unbind(vararg useCases: UseCase?) {
+ return lifecycleCameraProvider.unbind(*useCases)
+ }
+
+ @MainThread
+ override fun unbindAll() {
+ return lifecycleCameraProvider.unbindAll()
+ }
+
+ @MainThread
+ override fun bindToLifecycle(
lifecycleOwner: LifecycleOwner,
cameraSelector: CameraSelector,
vararg useCases: UseCase?
- ): Camera =
- trace("CX:bindToLifecycle") {
- if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
- throw UnsupportedOperationException(
- "bindToLifecycle for single camera is not supported in concurrent camera mode, " +
- "call unbindAll() first"
- )
- }
- cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
- val camera =
- bindToLifecycle(
- lifecycleOwner,
- cameraSelector,
- null,
- CompositionSettings.DEFAULT,
- CompositionSettings.DEFAULT,
- null,
- emptyList<CameraEffect>(),
- *useCases
- )
- return@trace camera
- }
+ ): Camera {
+ return lifecycleCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, *useCases)
+ }
- /**
- * Binds a [UseCaseGroup] to a [LifecycleOwner].
- *
- * Similar to [bindToLifecycle], with the addition that the bound collection of [UseCase] share
- * parameters defined by [UseCaseGroup] such as consistent camera sensor rect across all
- * [UseCase]s.
- *
- * If one [UseCase] is in multiple [UseCaseGroup]s, it will be linked to the [UseCaseGroup] in
- * the latest [bindToLifecycle] call.
- *
- * @throws UnsupportedOperationException If the camera is configured in concurrent mode.
- */
@MainThread
- public override fun bindToLifecycle(
+ override fun bindToLifecycle(
lifecycleOwner: LifecycleOwner,
cameraSelector: CameraSelector,
useCaseGroup: UseCaseGroup
- ): Camera =
- trace("CX:bindToLifecycle-UseCaseGroup") {
- if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
- throw UnsupportedOperationException(
- "bindToLifecycle for single camera is not supported in concurrent camera mode, " +
- "call unbindAll() first."
- )
- }
- cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
- val camera =
- bindToLifecycle(
- lifecycleOwner,
- cameraSelector,
- null,
- CompositionSettings.DEFAULT,
- CompositionSettings.DEFAULT,
- useCaseGroup.viewPort,
- useCaseGroup.effects,
- *useCaseGroup.useCases.toTypedArray<UseCase>()
- )
- return@trace camera
- }
-
- /**
- * Binds list of [SingleCameraConfig]s to [LifecycleOwner].
- *
- * The concurrent camera is only supporting two cameras currently. If the input list of
- * [SingleCameraConfig]s have less or more than two [SingleCameraConfig]s,
- * [IllegalArgumentException] will be thrown. If cameras are already used by other [UseCase]s,
- * [UnsupportedOperationException] will be thrown.
- *
- * A logical camera is a grouping of two or more of those physical cameras. See
- * [Multi-camera API](https://developer.android.com/media/camera/camera2/multi-camera)
- *
- * If we want to open concurrent logical cameras, which are one front camera and one back
- * camera, the device needs to support [PackageManager.FEATURE_CAMERA_CONCURRENT]. To set up
- * concurrent logical camera, call [availableConcurrentCameraInfos] to get the list of available
- * combinations of concurrent cameras. Each sub-list contains the [CameraInfo]s for a
- * combination of cameras that can be operated concurrently. Each logical camera can have its
- * own [UseCase]s and [LifecycleOwner]. See
- * [CameraX lifecycles]({@docRoot}training/camerax/architecture#lifecycles)
- *
- * If the concurrent logical cameras are binding the same preview and video capture use cases,
- * the concurrent cameras video recording will be supported. The concurrent camera preview
- * stream will be shared with video capture and record the concurrent cameras streams as a
- * composited stream. The [CompositionSettings] can be used to configure the position of each
- * camera stream and different layouts can be built. See [CompositionSettings] for more details.
- *
- * If we want to open concurrent physical cameras, which are two front cameras or two back
- * cameras, the device needs to support physical cameras and the capability could be checked via
- * [CameraInfo.isLogicalMultiCameraSupported]. Each physical cameras can have its own [UseCase]s
- * but needs to have the same [LifecycleOwner], otherwise [IllegalArgumentException] will be
- * thrown.
- *
- * If we want to open one physical camera, for example ultra wide, we just need to set physical
- * camera id in [CameraSelector] and bind to lifecycle. All CameraX features will work normally
- * when only a single physical camera is used.
- *
- * If we want to open multiple physical cameras, we need to have multiple [CameraSelector]s,
- * each in one [SingleCameraConfig] and set physical camera id, then bind to lifecycle with the
- * [SingleCameraConfig]s. Internally each physical camera id will be set on [UseCase], for
- * example, [Preview] and call
- * [android.hardware.camera2.params.OutputConfiguration.setPhysicalCameraId].
- *
- * Currently only two physical cameras for the same logical camera id are allowed and the device
- * needs to support physical cameras by checking [CameraInfo.isLogicalMultiCameraSupported]. In
- * addition, there is no guarantee or API to query whether the device supports multiple physical
- * camera opening or not. Internally the library checks
- * [android.hardware.camera2.CameraDevice.isSessionConfigurationSupported], if the device does
- * not support the multiple physical camera configuration, [IllegalArgumentException] will be
- * thrown.
- *
- * @param singleCameraConfigs Input list of [SingleCameraConfig]s.
- * @return Output [ConcurrentCamera] instance.
- * @throws IllegalArgumentException If less or more than two camera configs are provided.
- * @throws UnsupportedOperationException If device is not supporting concurrent camera or
- * cameras are already used by other [UseCase]s.
- * @see ConcurrentCamera
- * @see availableConcurrentCameraInfos
- * @see CameraInfo.isLogicalMultiCameraSupported
- * @see CameraInfo.getPhysicalCameraInfos
- */
- @MainThread
- public override fun bindToLifecycle(
- singleCameraConfigs: List<SingleCameraConfig?>
- ): ConcurrentCamera =
- trace("CX:bindToLifecycle-Concurrent") {
- if (singleCameraConfigs.size < 2) {
- throw IllegalArgumentException("Concurrent camera needs two camera configs.")
- }
-
- if (singleCameraConfigs.size > 2) {
- throw IllegalArgumentException(
- "Concurrent camera is only supporting two cameras at maximum."
- )
- }
-
- val firstCameraConfig = singleCameraConfigs[0]!!
- val secondCameraConfig = singleCameraConfigs[1]!!
-
- val cameras: MutableList<Camera> = ArrayList()
- if (
- firstCameraConfig.cameraSelector.lensFacing ==
- secondCameraConfig.cameraSelector.lensFacing
- ) {
- if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
- throw UnsupportedOperationException(
- "Camera is already running, call unbindAll() before binding more cameras."
- )
- }
- if (
- firstCameraConfig.lifecycleOwner != secondCameraConfig.lifecycleOwner ||
- firstCameraConfig.useCaseGroup.viewPort !=
- secondCameraConfig.useCaseGroup.viewPort ||
- firstCameraConfig.useCaseGroup.effects !=
- secondCameraConfig.useCaseGroup.effects
- ) {
- throw IllegalArgumentException(
- "Two camera configs need to have the same lifecycle owner, view port and " +
- "effects."
- )
- }
- val lifecycleOwner = firstCameraConfig.lifecycleOwner
- val cameraSelector = firstCameraConfig.cameraSelector
- val viewPort = firstCameraConfig.useCaseGroup.viewPort
- val effects = firstCameraConfig.useCaseGroup.effects
- val useCases: MutableList<UseCase> = ArrayList()
- for (config: SingleCameraConfig? in singleCameraConfigs) {
- // Connect physical camera id with use case.
- for (useCase: UseCase in config!!.useCaseGroup.useCases) {
- config.cameraSelector.physicalCameraId?.let {
- useCase.setPhysicalCameraId(it)
- }
- }
- useCases.addAll(config.useCaseGroup.useCases)
- }
-
- cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
- val camera =
- bindToLifecycle(
- lifecycleOwner,
- cameraSelector,
- null,
- CompositionSettings.DEFAULT,
- CompositionSettings.DEFAULT,
- viewPort,
- effects,
- *useCases.toTypedArray<UseCase>()
- )
- cameras.add(camera)
- } else {
- if (!mContext!!.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
- throw UnsupportedOperationException(
- "Concurrent camera is not supported on the device."
- )
- }
-
- if (cameraOperatingMode == CAMERA_OPERATING_MODE_SINGLE) {
- throw UnsupportedOperationException(
- "Camera is already running, call unbindAll() before binding more cameras."
- )
- }
-
- val cameraInfosToBind: MutableList<CameraInfo> = ArrayList()
- val firstCameraInfo: CameraInfo
- val secondCameraInfo: CameraInfo
- try {
- firstCameraInfo = getCameraInfo(firstCameraConfig.cameraSelector)
- secondCameraInfo = getCameraInfo(secondCameraConfig.cameraSelector)
- } catch (e: IllegalArgumentException) {
- throw IllegalArgumentException("Invalid camera selectors in camera configs.")
- }
- cameraInfosToBind.add(firstCameraInfo)
- cameraInfosToBind.add(secondCameraInfo)
- if (
- activeConcurrentCameraInfos.isNotEmpty() &&
- cameraInfosToBind != activeConcurrentCameraInfos
- ) {
- throw UnsupportedOperationException(
- "Cameras are already running, call unbindAll() before binding more cameras."
- )
- }
-
- cameraOperatingMode = CAMERA_OPERATING_MODE_CONCURRENT
-
- // For dual camera video capture, we are only supporting two use cases:
- // Preview + VideoCapture. If ImageCapture support is added, the validation logic
- // will be updated accordingly.
- var isDualCameraVideoCapture = false
- if (
- Objects.equals(
- firstCameraConfig.useCaseGroup.useCases,
- secondCameraConfig.useCaseGroup.useCases
- ) && firstCameraConfig.useCaseGroup.useCases.size == 2
- ) {
- val useCase0 = firstCameraConfig.useCaseGroup.useCases[0]
- val useCase1 = firstCameraConfig.useCaseGroup.useCases[1]
- isDualCameraVideoCapture =
- (isVideoCapture(useCase0) && isPreview(useCase1)) ||
- (isPreview(useCase0) && isVideoCapture(useCase1))
- }
-
- if (isDualCameraVideoCapture) {
- cameras.add(
- bindToLifecycle(
- firstCameraConfig.lifecycleOwner,
- firstCameraConfig.cameraSelector,
- secondCameraConfig.cameraSelector,
- firstCameraConfig.compositionSettings,
- secondCameraConfig.compositionSettings,
- firstCameraConfig.useCaseGroup.viewPort,
- firstCameraConfig.useCaseGroup.effects,
- *firstCameraConfig.useCaseGroup.useCases.toTypedArray<UseCase>(),
- )
- )
- } else {
- for (config: SingleCameraConfig? in singleCameraConfigs) {
- val camera =
- bindToLifecycle(
- config!!.lifecycleOwner,
- config.cameraSelector,
- null,
- CompositionSettings.DEFAULT,
- CompositionSettings.DEFAULT,
- config.useCaseGroup.viewPort,
- config.useCaseGroup.effects,
- *config.useCaseGroup.useCases.toTypedArray<UseCase>()
- )
- cameras.add(camera)
- }
- }
- activeConcurrentCameraInfos = cameraInfosToBind
- }
- return@trace ConcurrentCamera(cameras)
- }
-
- private fun isVideoCapture(useCase: UseCase): Boolean {
- return useCase.currentConfig.containsOption(UseCaseConfig.OPTION_CAPTURE_TYPE) &&
- useCase.currentConfig.captureType == CaptureType.VIDEO_CAPTURE
+ ): Camera {
+ return lifecycleCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)
}
- private fun isPreview(useCase: UseCase): Boolean {
- return useCase is Preview
- }
-
- /**
- * Binds [ViewPort] and a collection of [UseCase] to a [LifecycleOwner].
- *
- * The state of the lifecycle will determine when the cameras are open, started, stopped and
- * closed. When started, the use cases receive camera data.
- *
- * Binding to a [LifecycleOwner] in state currently in [Lifecycle.State.STARTED] or greater will
- * also initialize and start data capture. If the camera was already running this may cause a
- * new initialization to occur temporarily stopping data from the camera before restarting it.
- *
- * Multiple use cases can be bound via adding them all to a single [bindToLifecycle] call, or by
- * using multiple [bindToLifecycle] calls. Using a single call that includes all the use cases
- * helps to set up a camera session correctly for all uses cases, such as by allowing
- * determination of resolutions depending on all the use cases bound being bound. If the use
- * cases are bound separately, it will find the supported resolution with the priority depending
- * on the binding sequence. If the use cases are bound with a single call, it will find the
- * supported resolution with the priority in sequence of [ImageCapture], [Preview] and then
- * [ImageAnalysis]. The resolutions that can be supported depends on the camera device hardware
- * level that there are some default guaranteed resolutions listed in
- * [android.hardware.camera2.CameraDevice.createCaptureSession].
- *
- * Currently up to 3 use cases may be bound to a [Lifecycle] at any time. Exceeding capability
- * of target camera device will throw an IllegalArgumentException.
- *
- * A [UseCase] should only be bound to a single lifecycle and camera selector a time. Attempting
- * to bind a use case to a lifecycle when it is already bound to another lifecycle is an error,
- * and the use case binding will not change. Attempting to bind the same use case to multiple
- * camera selectors is also an error and will not change the binding.
- *
- * If different use cases are bound to different camera selectors that resolve to distinct
- * cameras, but the same lifecycle, only one of the cameras will operate at a time. The
- * non-operating camera will not become active until it is the only camera with use cases bound.
- *
- * The [Camera] returned is determined by the given camera selector, plus other internal
- * requirements, possibly from use case configurations. The camera returned from
- * [bindToLifecycle] may differ from the camera determined solely by a camera selector. If the
- * camera selector can't resolve a camera under the requirements, an [IllegalArgumentException]
- * will be thrown.
- *
- * Only [UseCase] bound to latest active [Lifecycle] can keep alive. [UseCase] bound to other
- * [Lifecycle] will be stopped.
- *
- * @param lifecycleOwner The [LifecycleOwner] which controls the lifecycle transitions of the
- * use cases.
- * @param primaryCameraSelector The primary camera selector which determines the camera to use
- * for set of use cases.
- * @param secondaryCameraSelector The secondary camera selector in dual camera case.
- * @param primaryCompositionSettings The composition settings for the primary camera.
- * @param secondaryCompositionSettings The composition settings for the secondary camera.
- * @param viewPort The viewPort which represents the visible camera sensor rect.
- * @param effects The effects applied to the camera outputs.
- * @param useCases The use cases to bind to a lifecycle.
- * @return The [Camera] instance which is determined by the camera selector and internal
- * requirements.
- * @throws IllegalStateException If the use case has already been bound to another lifecycle or
- * method is not called on main thread.
- * @throws IllegalArgumentException If the provided camera selector is unable to resolve a
- * camera to be used for the given use cases.
- */
- @Suppress("unused")
- internal fun bindToLifecycle(
- lifecycleOwner: LifecycleOwner,
- primaryCameraSelector: CameraSelector,
- secondaryCameraSelector: CameraSelector?,
- primaryCompositionSettings: CompositionSettings,
- secondaryCompositionSettings: CompositionSettings,
- viewPort: ViewPort?,
- effects: List<CameraEffect?>,
- vararg useCases: UseCase?
- ): Camera =
- trace("CX:bindToLifecycle-internal") {
- Threads.checkMainThread()
- // TODO(b/153096869): override UseCase's target rotation.
-
- // Get the LifecycleCamera if existed.
- val primaryCameraInternal =
- primaryCameraSelector.select(mCameraX!!.cameraRepository.cameras)
- primaryCameraInternal.setPrimary(true)
- val primaryRestrictedCameraInfo =
- getCameraInfo(primaryCameraSelector) as RestrictedCameraInfo
-
- var secondaryCameraInternal: CameraInternal? = null
- var secondaryRestrictedCameraInfo: RestrictedCameraInfo? = null
- if (secondaryCameraSelector != null) {
- secondaryCameraInternal =
- secondaryCameraSelector.select(mCameraX!!.cameraRepository.cameras)
- secondaryCameraInternal.setPrimary(false)
- secondaryRestrictedCameraInfo =
- getCameraInfo(secondaryCameraSelector) as RestrictedCameraInfo
- }
-
- var lifecycleCameraToBind =
- mLifecycleCameraRepository.getLifecycleCamera(
- lifecycleOwner,
- CameraUseCaseAdapter.generateCameraId(
- primaryRestrictedCameraInfo,
- secondaryRestrictedCameraInfo
- )
- )
-
- // Check if there's another camera that has already been bound.
- val lifecycleCameras = mLifecycleCameraRepository.lifecycleCameras
- useCases.filterNotNull().forEach { useCase ->
- for (lifecycleCamera: LifecycleCamera in lifecycleCameras) {
- if (
- lifecycleCamera.isBound(useCase) && lifecycleCamera != lifecycleCameraToBind
- ) {
- throw IllegalStateException(
- String.format(
- "Use case %s already bound to a different lifecycle.",
- useCase
- )
- )
- }
- }
- }
-
- // Create the LifecycleCamera if there's no existing one that can be used.
- if (lifecycleCameraToBind == null) {
- lifecycleCameraToBind =
- mLifecycleCameraRepository.createLifecycleCamera(
- lifecycleOwner,
- CameraUseCaseAdapter(
- primaryCameraInternal,
- secondaryCameraInternal,
- primaryRestrictedCameraInfo,
- secondaryRestrictedCameraInfo,
- primaryCompositionSettings,
- secondaryCompositionSettings,
- mCameraX!!.cameraFactory.cameraCoordinator,
- mCameraX!!.cameraDeviceSurfaceManager,
- mCameraX!!.defaultConfigFactory
- )
- )
- }
-
- if (useCases.isEmpty()) {
- return@trace lifecycleCameraToBind!!
- }
-
- mLifecycleCameraRepository.bindToLifecycleCamera(
- lifecycleCameraToBind!!,
- viewPort,
- effects,
- listOf(*useCases),
- mCameraX!!.cameraFactory.cameraCoordinator
- )
-
- return@trace lifecycleCameraToBind
- }
-
- public override fun isBound(useCase: UseCase): Boolean {
- for (lifecycleCamera: LifecycleCamera in mLifecycleCameraRepository.lifecycleCameras) {
- if (lifecycleCamera.isBound(useCase)) {
- return true
- }
- }
-
- return false
- }
-
- /**
- * Unbinds all specified use cases from the lifecycle.
- *
- * This will initiate a close of every open camera which has zero [UseCase] associated with it
- * at the end of this call.
- *
- * If a use case in the argument list is not bound, then it is simply ignored.
- *
- * After unbinding a UseCase, the UseCase can be and bound to another [Lifecycle] however
- * listeners and settings should be reset by the application.
- *
- * @param useCases The collection of use cases to remove.
- * @throws IllegalStateException If not called on main thread.
- * @throws UnsupportedOperationException If called in concurrent mode.
- */
@MainThread
- public override fun unbind(vararg useCases: UseCase?): Unit =
- trace("CX:unbind") {
- Threads.checkMainThread()
-
- if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
- throw UnsupportedOperationException(
- "Unbind usecase is not supported in concurrent camera mode, call unbindAll() first."
- )
- }
-
- mLifecycleCameraRepository.unbind(listOf(*useCases))
- }
-
- @MainThread
- public override fun unbindAll(): Unit =
- trace("CX:unbindAll") {
- Threads.checkMainThread()
- cameraOperatingMode = CAMERA_OPERATING_MODE_UNSPECIFIED
- mLifecycleCameraRepository.unbindAll()
- }
-
- @Throws(CameraInfoUnavailableException::class)
- override fun hasCamera(cameraSelector: CameraSelector): Boolean =
- trace("CX:hasCamera") {
- try {
- cameraSelector.select(mCameraX!!.cameraRepository.cameras)
- } catch (e: IllegalArgumentException) {
- return@trace false
- }
-
- return@trace true
- }
+ override fun bindToLifecycle(
+ singleCameraConfigs: List<ConcurrentCamera.SingleCameraConfig?>
+ ): ConcurrentCamera {
+ return lifecycleCameraProvider.bindToLifecycle(singleCameraConfigs)
+ }
override val availableCameraInfos: List<CameraInfo>
- get() =
- trace("CX:getAvailableCameraInfos") {
- val availableCameraInfos: MutableList<CameraInfo> = ArrayList()
- val cameras: Set<CameraInternal> = mCameraX!!.cameraRepository.cameras
- for (camera: CameraInternal in cameras) {
- availableCameraInfos.add(camera.cameraInfo)
- }
- return@trace availableCameraInfos
- }
+ get() = lifecycleCameraProvider.availableCameraInfos
final override val availableConcurrentCameraInfos: List<List<CameraInfo>>
- get() =
- trace("CX:getAvailableConcurrentCameraInfos") {
- requireNonNull(mCameraX)
- requireNonNull(mCameraX!!.cameraFactory.cameraCoordinator)
- val concurrentCameraSelectorLists =
- mCameraX!!.cameraFactory.cameraCoordinator.concurrentCameraSelectors
-
- val availableConcurrentCameraInfos: MutableList<List<CameraInfo>> = ArrayList()
- for (cameraSelectors in concurrentCameraSelectorLists) {
- val cameraInfos: MutableList<CameraInfo> = ArrayList()
- for (cameraSelector in cameraSelectors) {
- var cameraInfo: CameraInfo
- try {
- cameraInfo = getCameraInfo(cameraSelector)
- } catch (e: IllegalArgumentException) {
- continue
- }
- cameraInfos.add(cameraInfo)
- }
- availableConcurrentCameraInfos.add(cameraInfos)
- }
- return@trace availableConcurrentCameraInfos
- }
-
- override fun getCameraInfo(cameraSelector: CameraSelector): CameraInfo =
- trace("CX:getCameraInfo") {
- val cameraInfoInternal =
- cameraSelector.select(mCameraX!!.cameraRepository.cameras).cameraInfoInternal
- val cameraConfig = getCameraConfig(cameraSelector, cameraInfoInternal)
-
- val key =
- CameraUseCaseAdapter.CameraId.create(
- cameraInfoInternal.cameraId,
- cameraConfig.compatibilityId
- )
- var restrictedCameraInfo: RestrictedCameraInfo?
- synchronized(mLock) {
- restrictedCameraInfo = mCameraInfoMap[key]
- if (restrictedCameraInfo == null) {
- restrictedCameraInfo = RestrictedCameraInfo(cameraInfoInternal, cameraConfig)
- mCameraInfoMap[key] = restrictedCameraInfo!!
- }
- }
-
- return@trace restrictedCameraInfo!!
- }
+ get() = lifecycleCameraProvider.availableConcurrentCameraInfos
final override val isConcurrentCameraModeOn: Boolean
- @MainThread get() = cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT
+ @MainThread get() = lifecycleCameraProvider.isConcurrentCameraModeOn
- private fun getOrCreateCameraXInstance(context: Context): ListenableFuture<CameraX> {
- synchronized(mLock) {
- if (mCameraXInitializeFuture != null) {
- return mCameraXInitializeFuture as ListenableFuture<CameraX>
- }
- val cameraX = CameraX(context, mCameraXConfigProvider)
-
- mCameraXInitializeFuture =
- CallbackToFutureAdapter.getFuture { completer ->
- synchronized(mLock) {
- val future: ListenableFuture<Void> =
- FutureChain.from(mCameraXShutdownFuture)
- .transformAsync(
- { cameraX.initializeFuture },
- CameraXExecutors.directExecutor()
- )
- Futures.addCallback(
- future,
- object : FutureCallback<Void?> {
- override fun onSuccess(result: Void?) {
- completer.set(cameraX)
- }
-
- override fun onFailure(t: Throwable) {
- completer.setException(t)
- }
- },
- CameraXExecutors.directExecutor()
- )
- }
-
- "ProcessCameraProvider-initializeCameraX"
- }
-
- return mCameraXInitializeFuture as ListenableFuture<CameraX>
- }
+ @Throws(CameraInfoUnavailableException::class)
+ override fun hasCamera(cameraSelector: CameraSelector): Boolean {
+ return lifecycleCameraProvider.hasCamera(cameraSelector)
}
- private fun configureInstanceInternal(cameraXConfig: CameraXConfig) =
- trace("CX:configureInstanceInternal") {
- synchronized(mLock) {
- Preconditions.checkNotNull(cameraXConfig)
- Preconditions.checkState(
- mCameraXConfigProvider == null,
- "CameraX has already been configured. To use a different configuration, " +
- "shutdown() must be called."
- )
- mCameraXConfigProvider = CameraXConfig.Provider { cameraXConfig }
- }
- }
-
- private fun getCameraConfig(
- cameraSelector: CameraSelector,
- cameraInfo: CameraInfo
- ): CameraConfig {
- var cameraConfig: CameraConfig? = null
- for (cameraFilter: CameraFilter in cameraSelector.cameraFilterSet) {
- if (cameraFilter.identifier != CameraFilter.DEFAULT_ID) {
- val extendedCameraConfig =
- ExtendedCameraConfigProviderStore.getConfigProvider(cameraFilter.identifier)
- .getConfig(cameraInfo, (mContext)!!)
- if (extendedCameraConfig == null) { // ignore IDs unrelated to camera configs.
- continue
- }
-
- // Only allows one camera config now.
- if (cameraConfig != null) {
- throw IllegalArgumentException(
- "Cannot apply multiple extended camera configs at the same time."
- )
- }
- cameraConfig = extendedCameraConfig
- }
- }
-
- if (cameraConfig == null) {
- cameraConfig = CameraConfigs.defaultConfig()
- }
- return cameraConfig
+ override fun getCameraInfo(cameraSelector: CameraSelector): CameraInfo {
+ return lifecycleCameraProvider.getCameraInfo(cameraSelector)
}
- private fun setCameraX(cameraX: CameraX) {
- mCameraX = cameraX
+ // TODO: Remove the annotation when LifecycleCameraProvider is ready to be public.
+ @VisibleForTesting
+ override fun shutdownAsync(): ListenableFuture<Void> {
+ return lifecycleCameraProvider.shutdownAsync()
}
- private fun setContext(context: Context) {
- mContext = context
+ private fun initAsync(context: Context): ListenableFuture<Void> {
+ return lifecycleCameraProvider.initAsync(context, null)
}
- @get:CameraOperatingMode
- private var cameraOperatingMode: Int
- get() {
- if (mCameraX == null) {
- return CAMERA_OPERATING_MODE_UNSPECIFIED
- }
- return mCameraX!!.cameraFactory.cameraCoordinator.cameraOperatingMode
- }
- private set(cameraOperatingMode) {
- if (mCameraX == null) {
- return
- }
- mCameraX!!.cameraFactory.cameraCoordinator.cameraOperatingMode = cameraOperatingMode
- }
-
- private var activeConcurrentCameraInfos: List<CameraInfo>
- get() {
- if (mCameraX == null) {
- return java.util.ArrayList()
- }
- return mCameraX!!.cameraFactory.cameraCoordinator.activeConcurrentCameraInfos
- }
- private set(cameraInfos) {
- if (mCameraX == null) {
- return
- }
- mCameraX!!.cameraFactory.cameraCoordinator.activeConcurrentCameraInfos = cameraInfos
- }
+ private fun configure(cameraXConfig: CameraXConfig) {
+ return lifecycleCameraProvider.configure(cameraXConfig)
+ }
public companion object {
- private val sAppInstance = ProcessCameraProvider()
+ private val sAppInstance = ProcessCameraProvider(LifecycleCameraProviderImpl())
/**
* Retrieves the ProcessCameraProvider associated with the current process.
@@ -859,12 +170,8 @@
public fun getInstance(context: Context): ListenableFuture<ProcessCameraProvider> {
Preconditions.checkNotNull(context)
return Futures.transform(
- sAppInstance.getOrCreateCameraXInstance(context),
- { cameraX ->
- sAppInstance.setCameraX(cameraX)
- sAppInstance.setContext(ContextUtil.getApplicationContext(context))
- sAppInstance
- },
+ sAppInstance.initAsync(context),
+ { sAppInstance },
CameraXExecutors.directExecutor()
)
}
@@ -892,7 +199,7 @@
* method from library code is not recommended** as the application owner should ultimately
* be in control of singleton configuration.
*
- * @param cameraXConfig configuration options for the singleton process camera provider
+ * @param cameraXConfig The configuration options for the singleton process camera provider
* instance.
* @throws IllegalStateException If the camera provider has already been configured by a
* previous call to `configureInstance()` or [getInstance].
@@ -900,6 +207,6 @@
@JvmStatic
@ExperimentalCameraProviderConfiguration
public fun configureInstance(cameraXConfig: CameraXConfig): Unit =
- trace("CX:configureInstance") { sAppInstance.configureInstanceInternal(cameraXConfig) }
+ trace("CX:configureInstance") { sAppInstance.configure(cameraXConfig) }
}
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeImageCaptureCallback.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeOnImageCapturedCallback.kt
similarity index 64%
rename from camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeImageCaptureCallback.kt
rename to camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeOnImageCapturedCallback.kt
index b600a40..135f64c 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeImageCaptureCallback.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeOnImageCapturedCallback.kt
@@ -25,14 +25,25 @@
import androidx.camera.core.impl.utils.Exif
import com.google.common.truth.Truth
import java.io.ByteArrayInputStream
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.withTimeoutOrNull
-private const val CAPTURE_TIMEOUT = 15_000.toLong() // 15 seconds
+/**
+ * A fake implementation of the [ImageCapture.OnImageCapturedCallback] that is used for test.
+ *
+ * @param captureCount Number of captures to wait for.
+ * @property closeImageOnSuccess Whether to close images immediately on [onCaptureSuccess]
+ * callbacks. This is true by default. If set to false, it is the user's responsibility to close
+ * the images.
+ */
+public class FakeOnImageCapturedCallback(
+ captureCount: Int = 1,
+ private val closeImageOnSuccess: Boolean = true
+) : ImageCapture.OnImageCapturedCallback() {
+ public data class CapturedImage(val image: ImageProxy, val properties: ImageProperties)
-/** A fake implementation of the [ImageCapture.OnImageCapturedCallback] and used for test. */
-public class FakeImageCaptureCallback(captureCount: Int = 1) :
- ImageCapture.OnImageCapturedCallback() {
/** Data class of various image properties which are tested. */
public data class ImageProperties(
val size: Size? = null,
@@ -43,20 +54,34 @@
)
private val latch = CountdownDeferred(captureCount)
- public val results: MutableList<ImageProperties> = mutableListOf()
+
+ /**
+ * List of [CapturedImage] obtained in [onCaptureSuccess] callback.
+ *
+ * If [closeImageOnSuccess] is true, the [CapturedImage.image] will be closed as soon as
+ * `onCaptureSuccess` is invoked. Otherwise, it will be the user's responsibility to close the
+ * images.
+ */
+ public val results: MutableList<CapturedImage> = mutableListOf()
public val errors: MutableList<ImageCaptureException> = mutableListOf()
override fun onCaptureSuccess(image: ImageProxy) {
results.add(
- ImageProperties(
- size = Size(image.width, image.height),
- format = image.format,
- rotationDegrees = image.imageInfo.rotationDegrees,
- cropRect = image.cropRect,
- exif = getExif(image),
+ CapturedImage(
+ image = image,
+ properties =
+ ImageProperties(
+ size = Size(image.width, image.height),
+ format = image.format,
+ rotationDegrees = image.imageInfo.rotationDegrees,
+ cropRect = image.cropRect,
+ exif = getExif(image),
+ )
)
)
- image.close()
+ if (closeImageOnSuccess) {
+ image.close()
+ }
latch.countDown()
}
@@ -76,12 +101,12 @@
return null
}
- public suspend fun awaitCaptures(timeout: Long = CAPTURE_TIMEOUT) {
+ public suspend fun awaitCaptures(timeout: Duration = CAPTURE_TIMEOUT) {
Truth.assertThat(withTimeoutOrNull(timeout) { latch.await() }).isNotNull()
}
public suspend fun awaitCapturesAndAssert(
- timeout: Long = CAPTURE_TIMEOUT,
+ timeout: Duration = CAPTURE_TIMEOUT,
capturedImagesCount: Int = 0,
errorsCount: Int = 0
) {
@@ -110,4 +135,8 @@
deferredItems.forEach { it.await() }
}
}
+
+ public companion object {
+ private val CAPTURE_TIMEOUT = 15.seconds
+ }
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeOnImageSavedCallback.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeOnImageSavedCallback.kt
new file mode 100644
index 0000000..4ddec70
--- /dev/null
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeOnImageSavedCallback.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.testing.impl.fakes
+
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCaptureException
+import com.google.common.truth.Truth
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.withTimeoutOrNull
+
+/**
+ * A fake implementation of the [ImageCapture.OnImageCapturedCallback] that is used for test.
+ *
+ * @param captureCount Number of captures to wait for.
+ */
+public class FakeOnImageSavedCallback(captureCount: Int = 1) : ImageCapture.OnImageSavedCallback {
+ private val latch = CountdownDeferred(captureCount)
+ public val results: MutableList<ImageCapture.OutputFileResults> = mutableListOf()
+ public val errors: MutableList<ImageCaptureException> = mutableListOf()
+
+ override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
+ results.add(outputFileResults)
+ latch.countDown()
+ }
+
+ override fun onError(exception: ImageCaptureException) {
+ errors.add(exception)
+ latch.countDown()
+ }
+
+ public suspend fun awaitCaptures(timeout: Duration = CAPTURE_TIMEOUT) {
+ Truth.assertThat(withTimeoutOrNull(timeout) { latch.await() }).isNotNull()
+ }
+
+ public suspend fun awaitCapturesAndAssert(
+ timeout: Duration = CAPTURE_TIMEOUT,
+ capturedImagesCount: Int = 0,
+ errorsCount: Int = 0
+ ) {
+ Truth.assertThat(withTimeoutOrNull(timeout) { latch.await() }).isNotNull()
+ Truth.assertThat(results.size).isEqualTo(capturedImagesCount)
+ Truth.assertThat(errors.size).isEqualTo(errorsCount)
+ }
+
+ private class CountdownDeferred(val count: Int) {
+
+ private val deferredItems =
+ mutableListOf<CompletableDeferred<Unit>>().apply {
+ repeat(count) { add(CompletableDeferred()) }
+ }
+ private var index = 0
+
+ fun countDown() {
+ if (index < count) {
+ deferredItems[index++].complete(Unit)
+ } else {
+ throw IllegalStateException("Countdown already finished")
+ }
+ }
+
+ suspend fun await() {
+ deferredItems.forEach { it.await() }
+ }
+ }
+
+ public companion object {
+ private val CAPTURE_TIMEOUT = 15.seconds
+ }
+}
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/DeviceCompatibilityTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/DeviceCompatibilityTest.kt
deleted file mode 100644
index c8a2dbb..0000000
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/DeviceCompatibilityTest.kt
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * 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.camera.video
-
-import android.content.Context
-import android.media.MediaCodec
-import android.media.MediaCodecInfo
-import android.os.Build
-import androidx.camera.camera2.Camera2Config
-import androidx.camera.camera2.pipe.integration.CameraPipeConfig
-import androidx.camera.core.CameraSelector
-import androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA
-import androidx.camera.core.CameraSelector.DEFAULT_FRONT_CAMERA
-import androidx.camera.core.CameraXConfig
-import androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy
-import androidx.camera.testing.impl.CameraPipeConfigTestRule
-import androidx.camera.testing.impl.CameraUtil
-import androidx.camera.testing.impl.CameraXUtil
-import androidx.camera.video.internal.VideoValidatedEncoderProfilesProxy
-import androidx.camera.video.internal.compat.quirk.DeviceQuirks
-import androidx.camera.video.internal.compat.quirk.MediaCodecInfoReportIncorrectInfoQuirk
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.filters.SdkSuppress
-import androidx.test.filters.SmallTest
-import com.google.common.collect.Range
-import com.google.common.truth.Truth.assertWithMessage
-import java.util.concurrent.TimeUnit
-import org.junit.After
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-/**
- * Test used to find out compatibility issue.
- *
- * All tests in this file should always pass. Every time we want to add a new test, it means there
- * is also a corresponding quirk in the codebase and we want to find more devices with the same root
- * cause. Tests should use [assumeTrue] to skip related quirks so that the problematic device will
- * pass the test. Once a new failure is found in the mobile harness test results, we should add the
- * device to the relevant quirk to pass the test.
- */
-@SmallTest
-@RunWith(Parameterized::class)
-@SdkSuppress(minSdkVersion = 21)
-class DeviceCompatibilityTest(
- private val implName: String,
- private val cameraConfig: CameraXConfig,
-) {
-
- private val context: Context = ApplicationProvider.getApplicationContext()
- private val zeroRange by lazy { android.util.Range.create(0, 0) }
-
- @get:Rule
- val cameraPipeConfigTestRule =
- CameraPipeConfigTestRule(
- active = implName == CameraPipeConfig::class.simpleName,
- )
-
- @get:Rule
- val cameraRule =
- CameraUtil.grantCameraPermissionAndPreTestAndPostTest(
- CameraUtil.PreTestCameraIdList(cameraConfig)
- )
-
- @Before
- fun setup() {
- CameraXUtil.initialize(context, cameraConfig).get()
- }
-
- @After
- fun tearDown() {
- CameraXUtil.shutdown().get(10, TimeUnit.SECONDS)
- }
-
- @Test
- fun mediaCodecInfoShouldSupportEncoderProfilesSizes() {
- assumeTrue(DeviceQuirks.get(MediaCodecInfoReportIncorrectInfoQuirk::class.java) == null)
-
- // Arrange: Collect all supported profiles from default back/front camera.
- val supportedProfiles = mutableListOf<VideoValidatedEncoderProfilesProxy>()
- supportedProfiles.addAll(getSupportedProfiles(DEFAULT_BACK_CAMERA))
- supportedProfiles.addAll(getSupportedProfiles(DEFAULT_FRONT_CAMERA))
- assumeTrue(supportedProfiles.isNotEmpty())
-
- supportedProfiles.forEach { profile ->
- // Arrange: Find the codec and its video capabilities.
- // If mime is null, skip the test instead of failing it since this isn't the purpose
- // of the test.
- val videoProfile = profile.defaultVideoProfile
- val mime = videoProfile.mediaType
- if (mime == VideoProfileProxy.MEDIA_TYPE_NONE) {
- return@forEach
- }
- val capabilities =
- MediaCodec.createEncoderByType(mime).let { codec ->
- try {
- codec.codecInfo.getCapabilitiesForType(mime).videoCapabilities
- } finally {
- codec.release()
- }
- }
-
- // Act.
- val (width, height) = videoProfile.width to videoProfile.height
- // Pass if VideoCapabilities.isSizeSupported() is true
- if (capabilities.isSizeSupported(width, height)) {
- return@forEach
- }
-
- val supportedWidths = capabilities.supportedWidths
- val supportedHeights = capabilities.supportedHeights
- val supportedWidthsForHeight = capabilities.getWidthsForHeightQuietly(height)
- val supportedHeightForWidth = capabilities.getHeightsForWidthQuietly(width)
-
- // Assert.
- val msg =
- "Build.BRAND: ${Build.BRAND}, Build.MODEL: ${Build.MODEL} " +
- "mime: $mime, size: ${width}x$height is not in " +
- "supported widths $supportedWidths/$supportedWidthsForHeight " +
- "or heights $supportedHeights/$supportedHeightForWidth, " +
- "the width/height alignment is " +
- "${capabilities.widthAlignment}/${capabilities.heightAlignment}."
- assertWithMessage(msg).that(width).isIn(supportedWidths.toClosed())
- assertWithMessage(msg).that(height).isIn(supportedHeights.toClosed())
- assertWithMessage(msg).that(width).isIn(supportedWidthsForHeight.toClosed())
- assertWithMessage(msg).that(height).isIn(supportedHeightForWidth.toClosed())
- }
- }
-
- private fun getSupportedProfiles(
- cameraSelector: CameraSelector
- ): List<VideoValidatedEncoderProfilesProxy> {
- if (!CameraUtil.hasCameraWithLensFacing(cameraSelector.lensFacing!!)) {
- return emptyList()
- }
-
- val cameraInfo = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector).cameraInfo
- val videoCapabilities = Recorder.getVideoCapabilities(cameraInfo)
-
- return videoCapabilities.supportedDynamicRanges.flatMap { dynamicRange ->
- videoCapabilities.getSupportedQualities(dynamicRange).map { quality ->
- videoCapabilities.getProfiles(quality, dynamicRange)!!
- }
- }
- }
-
- private fun android.util.Range<Int>.toClosed() = Range.closed(lower, upper)
-
- private fun MediaCodecInfo.VideoCapabilities.getWidthsForHeightQuietly(
- height: Int
- ): android.util.Range<Int> {
- return try {
- getSupportedWidthsFor(height)
- } catch (e: IllegalArgumentException) {
- zeroRange
- }
- }
-
- private fun MediaCodecInfo.VideoCapabilities.getHeightsForWidthQuietly(
- width: Int
- ): android.util.Range<Int> {
- return try {
- getSupportedHeightsFor(width)
- } catch (e: IllegalArgumentException) {
- zeroRange
- }
- }
-
- companion object {
- @JvmStatic
- @Parameterized.Parameters(name = "{0}")
- fun data() =
- listOf(
- arrayOf(Camera2Config::class.simpleName, Camera2Config.defaultConfig()),
- arrayOf(CameraPipeConfig::class.simpleName, CameraPipeConfig.defaultConfig())
- )
- }
-}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
index 0775266..8e4f80d 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
@@ -140,6 +140,11 @@
SizeCannotEncodeVideoQuirk.load())) {
quirks.add(new SizeCannotEncodeVideoQuirk());
}
+ if (quirkSettings.shouldEnableQuirk(
+ PreviewBlackScreenQuirk.class,
+ PreviewBlackScreenQuirk.load())) {
+ quirks.add(new PreviewBlackScreenQuirk());
+ }
return quirks;
}
}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/PreviewBlackScreenQuirk.kt b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/PreviewBlackScreenQuirk.kt
new file mode 100644
index 0000000..67ed713
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/PreviewBlackScreenQuirk.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal.compat.quirk
+
+import android.annotation.SuppressLint
+import android.os.Build
+import androidx.camera.core.internal.compat.quirk.SurfaceProcessingQuirk
+
+/**
+ * QuirkSummary
+ * - Bug Id: b/361477717
+ * - Description: Quirk indicates Preview is black screen when binding with VideoCapture.
+ * - Device(s): Motorola Edge 20 Fusion.
+ */
+@SuppressLint("CameraXQuirksClassDetector")
+public class PreviewBlackScreenQuirk : SurfaceProcessingQuirk {
+
+ public companion object {
+
+ @JvmStatic
+ public fun load(): Boolean {
+ return isMotorolaEdge20Fusion
+ }
+
+ private val isMotorolaEdge20Fusion: Boolean =
+ Build.BRAND.equals("motorola", ignoreCase = true) &&
+ Build.MODEL.equals("motorola edge 20 fusion", ignoreCase = true)
+ }
+}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
index 346e8e9..4c1e375 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
@@ -84,8 +84,8 @@
import androidx.camera.testing.impl.CoreAppTestUtil
import androidx.camera.testing.impl.SurfaceTextureProvider
import androidx.camera.testing.impl.WakelockEmptyActivityRule
-import androidx.camera.testing.impl.fakes.FakeImageCaptureCallback
import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
+import androidx.camera.testing.impl.fakes.FakeOnImageCapturedCallback
import androidx.camera.testing.impl.fakes.FakeSessionProcessor
import androidx.camera.testing.impl.mocks.MockScreenFlash
import androidx.camera.video.Recorder
@@ -103,6 +103,8 @@
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.abs
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
@@ -126,7 +128,7 @@
private val BACK_SELECTOR = CameraSelector.DEFAULT_BACK_CAMERA
private val FRONT_SELECTOR = CameraSelector.DEFAULT_FRONT_CAMERA
private const val BACK_LENS_FACING = CameraSelector.LENS_FACING_BACK
-private const val CAPTURE_TIMEOUT = 15_000.toLong() // 15 seconds
+private val CAPTURE_TIMEOUT = 15.seconds
private const val TOLERANCE = 1e-3f
private val EXIF_GAINMAP_PATTERNS =
listOf(
@@ -231,14 +233,14 @@
}
// Act.
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
useCase.takePicture(mainExecutor, callback)
// Assert.
// Wait for the signal that the image has been captured.
callback.awaitCapturesAndAssert(capturedImagesCount = 1)
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
var sizeEnvelope = imageProperties.size
// Some devices may not be able to fit the requested resolution. In this case, the returned
@@ -311,13 +313,13 @@
if (camera.cameraInfo.isZslSupported) {
val numImages = 5
- val callback = FakeImageCaptureCallback(captureCount = numImages)
+ val callback = FakeOnImageCapturedCallback(captureCount = numImages)
for (i in 0 until numImages) {
useCase.takePicture(mainExecutor, callback)
}
callback.awaitCapturesAndAssert(
- timeout = numImages * CAPTURE_TIMEOUT,
+ timeout = CAPTURE_TIMEOUT.times(numImages),
capturedImagesCount = numImages
)
}
@@ -401,12 +403,12 @@
}
// Act.
- val callback = FakeImageCaptureCallback(captureCount = numImages)
+ val callback = FakeOnImageCapturedCallback(captureCount = numImages)
repeat(numImages) { useCase.takePicture(mainExecutor, callback) }
// Assert.
callback.awaitCapturesAndAssert(
- timeout = numImages * CAPTURE_TIMEOUT,
+ timeout = CAPTURE_TIMEOUT.times(numImages),
capturedImagesCount = numImages
)
}
@@ -463,7 +465,7 @@
}
// Act.
- val callback = FakeImageCaptureCallback()
+ val callback = FakeOnImageCapturedCallback()
imageCapture.takePicture(mainExecutor, callback)
// Assert.
@@ -823,7 +825,7 @@
// Wait for the signal that all the images have been saved.
callback.awaitCapturesAndAssert(
- timeout = numImages * CAPTURE_TIMEOUT,
+ timeout = CAPTURE_TIMEOUT.times(numImages),
savedImagesCount = numImages
)
}
@@ -882,7 +884,7 @@
cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
}
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
useCase.takePicture(mainExecutor, callback)
@@ -913,14 +915,14 @@
cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
}
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
useCase.takePicture(mainExecutor, callback)
// Wait for the signal that the image has been captured.
callback.awaitCapturesAndAssert(capturedImagesCount = 1)
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
assertThat(imageProperties.format).isEqualTo(ImageFormat.RAW10)
}
@@ -1037,13 +1039,13 @@
// directly know onStateAttached() callback has been received. Therefore, taking a
// picture and waiting for the capture success callback to know the use case's
// onStateAttached() callback has been received.
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
imageCapture.takePicture(mainExecutor, callback)
// Wait for the signal that the image has been captured.
callback.awaitCapturesAndAssert(capturedImagesCount = 1)
- val callback2 = FakeImageCaptureCallback(captureCount = 3)
+ val callback2 = FakeOnImageCapturedCallback(captureCount = 3)
imageCapture.takePicture(mainExecutor, callback2)
imageCapture.takePicture(mainExecutor, callback2)
imageCapture.takePicture(mainExecutor, callback2)
@@ -1066,7 +1068,7 @@
cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, imageCapture)
}
- val callback = FakeImageCaptureCallback(captureCount = 3)
+ val callback = FakeOnImageCapturedCallback(captureCount = 3)
imageCapture.takePicture(mainExecutor, callback)
imageCapture.takePicture(mainExecutor, callback)
imageCapture.takePicture(mainExecutor, callback)
@@ -1094,7 +1096,7 @@
@Test
fun takePictureReturnsErrorNO_CAMERA_whenNotBound() = runBlocking {
val imageCapture = ImageCapture.Builder().build()
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
imageCapture.takePicture(mainExecutor, callback)
@@ -1243,7 +1245,7 @@
cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
}
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
useCase.takePicture(mainExecutor, callback)
// Wait for the signal that the image has been captured.
@@ -1253,7 +1255,7 @@
// same as original one.
val expectedCroppingRatio = Rational(DEFAULT_RESOLUTION.width, DEFAULT_RESOLUTION.height)
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
val cropRect = imageProperties.cropRect
// Rotate the captured ImageProxy's crop rect into the coordinate space of the final
@@ -1280,7 +1282,7 @@
cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
}
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
// Checks camera device sensor degrees to set target cropping aspect ratio match the
// sensor orientation.
@@ -1298,7 +1300,7 @@
// After target rotation is updated, the result cropping aspect ratio should still the
// same as original one.
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
val cropRect = imageProperties.cropRect
// Rotate the captured ImageProxy's crop rect into the coordinate space of the final
@@ -1353,7 +1355,7 @@
cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCaseGroup)
}
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
useCase.takePicture(mainExecutor, callback)
@@ -1362,7 +1364,7 @@
// After target rotation is updated, the result cropping aspect ratio should still the
// same as original one.
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
val cropRect = imageProperties.cropRect
// Rotate the captured ImageProxy's crop rect into the coordinate space of the final
@@ -1447,13 +1449,13 @@
cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
}
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
useCase.takePicture(mainExecutor, callback)
// Wait for the signal that the image has been captured.
callback.awaitCapturesAndAssert(capturedImagesCount = 1)
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
val cropRect = imageProperties.cropRect
val cropRectAspectRatio = Rational(cropRect!!.height(), cropRect.width())
@@ -1610,13 +1612,13 @@
)
}
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
useCase.takePicture(mainExecutor, callback)
// Wait for the signal that the image has been captured.
callback.awaitCapturesAndAssert(capturedImagesCount = 1)
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
// Check the output image rotation degrees value is correct.
assertThat(imageProperties.rotationDegrees)
@@ -1683,13 +1685,13 @@
)
}
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
useCase.takePicture(mainExecutor, callback)
// Wait for the signal that the image has been captured.
callback.awaitCapturesAndAssert(capturedImagesCount = 1)
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
// Check the output image rotation degrees value is correct.
assertThat(imageProperties.rotationDegrees)
.isEqualTo(camera.cameraInfo.getSensorRotationDegrees(useCase.targetRotation))
@@ -1721,13 +1723,13 @@
)
}
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
useCase.takePicture(mainExecutor, callback)
// Wait for the signal that the image has been captured.
callback.awaitCapturesAndAssert(capturedImagesCount = 1)
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
// Check the output image rotation degrees value is correct.
assertThat(imageProperties.rotationDegrees)
@@ -1771,13 +1773,13 @@
)
}
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
imageCapture.takePicture(mainExecutor, callback)
// Wait for the signal that the image has been captured.
callback.awaitCapturesAndAssert(capturedImagesCount = 1)
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
// Check the output image rotation degrees value is correct.
assertThat(imageProperties.rotationDegrees)
@@ -1819,13 +1821,13 @@
)
}
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
imageCapture.takePicture(mainExecutor, callback)
// Wait for the signal that the image has been captured.
callback.awaitCapturesAndAssert(capturedImagesCount = 1)
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
// Check the output image rotation degrees value is correct.
if (isRotationOptionSupportedDevice()) {
@@ -1981,7 +1983,7 @@
.isTrue()
// Act.
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
withContext(Dispatchers.Main) {
// Test the reproduce step in b/235119898
cameraProvider.unbind(preview)
@@ -2008,7 +2010,7 @@
}
// wait for camera to start by taking a picture
- val callback1 = FakeImageCaptureCallback(captureCount = 1)
+ val callback1 = FakeOnImageCapturedCallback(captureCount = 1)
imageCapture.takePicture(mainExecutor, callback1)
try {
callback1.awaitCapturesAndAssert(capturedImagesCount = 1)
@@ -2017,7 +2019,7 @@
}
// Act.
- val callback2 = FakeImageCaptureCallback(captureCount = 1)
+ val callback2 = FakeOnImageCapturedCallback(captureCount = 1)
withContext(Dispatchers.Main) {
cameraProvider.unbind(videoCapture)
imageCapture.takePicture(mainExecutor, callback2)
@@ -2169,13 +2171,13 @@
assertThat(imageCapture.resolutionInfo!!.resolution).isEqualTo(maxHighResolutionOutputSize)
- val callback = FakeImageCaptureCallback(captureCount = 1)
+ val callback = FakeOnImageCapturedCallback(captureCount = 1)
imageCapture.takePicture(mainExecutor, callback)
// Wait for the signal that the image has been captured.
callback.awaitCapturesAndAssert(capturedImagesCount = 1)
- val imageProperties = callback.results.first()
+ val imageProperties = callback.results.first().properties
assertThat(imageProperties.size).isEqualTo(maxHighResolutionOutputSize)
}
@@ -2313,7 +2315,7 @@
}
suspend fun awaitCapturesAndAssert(
- timeout: Long = CAPTURE_TIMEOUT,
+ timeout: Duration = CAPTURE_TIMEOUT,
savedImagesCount: Int = 0,
errorsCount: Int = 0
) {
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/fakecamera/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/fakecamera/ImageCaptureTest.kt
new file mode 100644
index 0000000..af52c44
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/fakecamera/ImageCaptureTest.kt
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.core.fakecamera
+
+import android.content.ContentValues
+import android.content.Context
+import android.os.Build
+import android.provider.MediaStore
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.fakes.FakeAppConfig
+import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.fakes.FakeCameraControl
+import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
+import androidx.camera.testing.impl.fakes.FakeOnImageCapturedCallback
+import androidx.camera.testing.impl.fakes.FakeOnImageSavedCallback
+import androidx.test.core.app.ApplicationProvider
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Tests using a fake camera instead of real camera by replacing the camera-camera2 layer with
+ * camera-testing layer.
+ *
+ * They are aimed to ensure that integration between camera-core and camera-testing work seamlessly.
+ */
+@RunWith(Parameterized::class)
+class ImageCaptureTest(
+ @CameraSelector.LensFacing private val lensFacing: Int,
+) {
+ @get:Rule
+ val temporaryFolder =
+ TemporaryFolder(ApplicationProvider.getApplicationContext<Context>().cacheDir)
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var cameraProvider: ProcessCameraProvider
+ private lateinit var camera: FakeCamera
+ private lateinit var cameraControl: FakeCameraControl
+ private lateinit var imageCapture: ImageCapture
+
+ @Before
+ fun setup() = runBlocking {
+ cameraProvider = getFakeConfigCameraProvider(context)
+ imageCapture = bindImageCapture()
+ }
+
+ @After
+ fun tearDown() = runBlocking {
+ if (::cameraProvider.isInitialized) {
+ withContext(Dispatchers.Main) { cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS] }
+ }
+ }
+
+ // Duplicate to ImageCaptureTest on core-test-app JVM tests, any change here may need to be
+ // reflected there too
+ @Test
+ fun canSubmitTakePictureRequest(): Unit = runBlocking {
+ val countDownLatch = CountDownLatch(1)
+ cameraControl.setOnNewCaptureRequestListener { countDownLatch.countDown() }
+
+ imageCapture.takePicture(CameraXExecutors.directExecutor(), FakeOnImageCapturedCallback())
+
+ assertThat(countDownLatch.await(3, TimeUnit.SECONDS)).isTrue()
+ }
+
+ // Duplicate to ImageCaptureTest on core-test-app JVM tests, any change here may need to be
+ // reflected there too
+ @Ignore("b/318314454")
+ @Test
+ fun canCreateBitmapFromTakenImage_whenImageCapturedCallbackIsUsed(): Unit = runBlocking {
+ val callback = FakeOnImageCapturedCallback()
+ imageCapture.takePicture(CameraXExecutors.directExecutor(), callback)
+ callback.awaitCapturesAndAssert(capturedImagesCount = 1)
+ callback.results.first().image.toBitmap()
+ }
+
+ // Duplicate to ImageCaptureTest on core-test-app JVM tests, any change here may need to be
+ // reflected there too
+ @Ignore("b/318314454")
+ @Test
+ fun canFindImage_whenFileStorageAndImageSavedCallbackIsUsed(): Unit = runBlocking {
+ val saveLocation = temporaryFolder.newFile()
+ val previousLength = saveLocation.length()
+ val callback = FakeOnImageSavedCallback()
+
+ imageCapture.takePicture(
+ ImageCapture.OutputFileOptions.Builder(saveLocation).build(),
+ CameraXExecutors.directExecutor(),
+ callback
+ )
+
+ callback.awaitCapturesAndAssert(capturedImagesCount = 1)
+ assertThat(saveLocation.length()).isGreaterThan(previousLength)
+ }
+
+ // Duplicate to ImageCaptureTest on androidTest/fakecamera/ImageCaptureTest, any change here may
+ // need to be reflected there too
+ @Ignore("b/318314454")
+ @Test
+ fun canFindImage_whenMediaStoreAndImageSavedCallbackIsUsed(): Unit = runBlocking {
+ val initialCount = getMediaStoreCameraXImageCount()
+ val callback = FakeOnImageSavedCallback()
+ imageCapture.takePicture(
+ createMediaStoreOutputOptions(),
+ CameraXExecutors.directExecutor(),
+ callback
+ )
+ callback.awaitCapturesAndAssert(capturedImagesCount = 1)
+ assertThat(getMediaStoreCameraXImageCount()).isEqualTo(initialCount + 1)
+ }
+
+ private suspend fun bindImageCapture(): ImageCapture {
+ val imageCapture = ImageCapture.Builder().build()
+
+ withContext(Dispatchers.Main) {
+ cameraProvider.bindToLifecycle(
+ FakeLifecycleOwner().apply { startAndResume() },
+ CameraSelector.Builder().requireLensFacing(lensFacing).build(),
+ imageCapture
+ )
+ }
+
+ camera =
+ when (lensFacing) {
+ CameraSelector.LENS_FACING_BACK -> FakeAppConfig.getBackCamera()
+ CameraSelector.LENS_FACING_FRONT -> FakeAppConfig.getFrontCamera()
+ else -> throw AssertionError("Unsupported lens facing: $lensFacing")
+ }
+ cameraControl = camera.cameraControl as FakeCameraControl
+
+ return imageCapture
+ }
+
+ private fun getFakeConfigCameraProvider(context: Context): ProcessCameraProvider {
+ var cameraProvider: ProcessCameraProvider? = null
+ val latch = CountDownLatch(1)
+ ProcessCameraProvider.configureInstance(FakeAppConfig.create())
+ ProcessCameraProvider.getInstance(context)
+ .addListener(
+ {
+ cameraProvider = ProcessCameraProvider.getInstance(context).get()
+ latch.countDown()
+ },
+ CameraXExecutors.directExecutor()
+ )
+
+ Truth.assertWithMessage("ProcessCameraProvider.getInstance timed out!")
+ .that(latch.await(5, TimeUnit.SECONDS))
+ .isTrue()
+
+ return cameraProvider!!
+ }
+
+ private fun createMediaStoreOutputOptions(): ImageCapture.OutputFileOptions {
+ // Create time stamped name and MediaStore entry.
+ val name =
+ FILENAME_PREFIX +
+ SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US)
+ .format(System.currentTimeMillis())
+ val contentValues =
+ ContentValues().apply {
+ put(MediaStore.MediaColumns.DISPLAY_NAME, name)
+ put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
+ put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
+ }
+ }
+
+ // Create output options object which contains file + metadata
+ return ImageCapture.OutputFileOptions.Builder(
+ context.contentResolver,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ contentValues
+ )
+ .build()
+ }
+
+ private fun getMediaStoreCameraXImageCount(): Int {
+ val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME)
+ val selection = "${MediaStore.Images.Media.DISPLAY_NAME} LIKE ?"
+ val selectionArgs = arrayOf("$FILENAME_PREFIX%")
+
+ val query =
+ ApplicationProvider.getApplicationContext<Context>()
+ .contentResolver
+ .query(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ projection,
+ selection,
+ selectionArgs,
+ null
+ )
+
+ return query?.use { cursor -> cursor.count } ?: 0
+ }
+
+ companion object {
+ private const val FILENAME_PREFIX = "cameraXPhoto"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "LensFacing = {0}")
+ fun data() =
+ listOf(
+ arrayOf(CameraSelector.LENS_FACING_BACK),
+ arrayOf(CameraSelector.LENS_FACING_FRONT),
+ )
+ }
+}
diff --git a/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/ImageCaptureTest.kt
index b09987b..459187c 100644
--- a/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/ImageCaptureTest.kt
@@ -16,9 +16,10 @@
package androidx.camera.integration.core
+import android.content.ContentValues
import android.content.Context
import android.os.Build
-import android.os.Looper
+import android.provider.MediaStore
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.impl.utils.executor.CameraXExecutors
@@ -27,13 +28,17 @@
import androidx.camera.testing.fakes.FakeAppConfig
import androidx.camera.testing.fakes.FakeCamera
import androidx.camera.testing.fakes.FakeCameraControl
-import androidx.camera.testing.impl.fakes.FakeImageCaptureCallback
import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
+import androidx.camera.testing.impl.fakes.FakeOnImageCapturedCallback
+import androidx.camera.testing.impl.fakes.FakeOnImageSavedCallback
import androidx.test.core.app.ApplicationProvider
import androidx.testutils.MainDispatcherRule
import com.google.common.truth.Truth.assertThat
+import java.text.SimpleDateFormat
+import java.util.Locale
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -42,9 +47,9 @@
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
+import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner
-import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
@@ -59,26 +64,20 @@
@get:Rule val mainDispatcherRule = MainDispatcherRule(testDispatcher)
+ @get:Rule
+ val temporaryFolder =
+ TemporaryFolder(ApplicationProvider.getApplicationContext<Context>().cacheDir)
+
private val context = ApplicationProvider.getApplicationContext<Context>()
private lateinit var cameraProvider: ProcessCameraProvider
private lateinit var camera: FakeCamera
private lateinit var cameraControl: FakeCameraControl
private lateinit var imageCapture: ImageCapture
- companion object {
- @JvmStatic
- @ParameterizedRobolectricTestRunner.Parameters(name = "LensFacing = {0}")
- fun data() =
- listOf(
- arrayOf(CameraSelector.LENS_FACING_BACK),
- arrayOf(CameraSelector.LENS_FACING_FRONT),
- )
- }
-
@Before
fun setup() {
+ cameraProvider = getFakeConfigCameraProvider(context)
imageCapture = bindImageCapture()
- assertThat(imageCapture).isNotNull()
}
@After
@@ -88,28 +87,64 @@
}
}
+ // Duplicate to ImageCaptureTest on androidTest/fakecamera/ImageCaptureTest, any change here may
+ // need to be reflected there too
@Test
fun canSubmitTakePictureRequest(): Unit = runTest {
val countDownLatch = CountDownLatch(1)
cameraControl.setOnNewCaptureRequestListener { countDownLatch.countDown() }
- imageCapture.takePicture(CameraXExecutors.directExecutor(), FakeImageCaptureCallback())
+ imageCapture.takePicture(CameraXExecutors.directExecutor(), FakeOnImageCapturedCallback())
assertThat(countDownLatch.await(3, TimeUnit.SECONDS)).isTrue()
}
- @Ignore("TODO: b/318314454")
+ // Duplicate to ImageCaptureTest on androidTest/fakecamera/ImageCaptureTest, any change here may
+ // need to be reflected there too
+ @Ignore("b/318314454")
@Test
- fun canTakeImage(): Unit = runTest {
- val callback = FakeImageCaptureCallback()
+ fun canCreateBitmapFromTakenImage_whenImageCapturedCallbackIsUsed(): Unit = runTest {
+ val callback = FakeOnImageCapturedCallback()
imageCapture.takePicture(CameraXExecutors.directExecutor(), callback)
- shadowOf(Looper.getMainLooper()).idle()
- callback.awaitCaptures()
+ callback.awaitCapturesAndAssert(capturedImagesCount = 1)
+ callback.results.first().image.toBitmap()
+ }
+
+ // Duplicate to ImageCaptureTest on androidTest/fakecamera/ImageCaptureTest, any change here may
+ // need to be reflected there too
+ @Ignore("b/318314454")
+ @Test
+ fun canFindImage_whenFileStorageAndImageSavedCallbackIsUsed(): Unit = runTest {
+ val saveLocation = temporaryFolder.newFile()
+ val previousLength = saveLocation.length()
+ val callback = FakeOnImageSavedCallback()
+
+ imageCapture.takePicture(
+ ImageCapture.OutputFileOptions.Builder(saveLocation).build(),
+ CameraXExecutors.directExecutor(),
+ callback
+ )
+
+ callback.awaitCapturesAndAssert(capturedImagesCount = 1)
+ assertThat(saveLocation.length()).isGreaterThan(previousLength)
+ }
+
+ // Duplicate to ImageCaptureTest on androidTest/fakecamera/ImageCaptureTest, any change here may
+ // need to be reflected there too
+ @Ignore("b/318314454")
+ @Test
+ fun canFindFakeImageUri_whenMediaStoreAndImageSavedCallbackIsUsed(): Unit = runBlocking {
+ val callback = FakeOnImageSavedCallback()
+ imageCapture.takePicture(
+ createMediaStoreOutputOptions(),
+ CameraXExecutors.directExecutor(),
+ callback
+ )
+ callback.awaitCapturesAndAssert(capturedImagesCount = 1)
+ assertThat(callback.results.first().savedUri).isNotNull()
}
private fun bindImageCapture(): ImageCapture {
- cameraProvider = getFakeConfigCameraProvider(context)
-
val imageCapture = ImageCapture.Builder().build()
cameraProvider.bindToLifecycle(
@@ -127,4 +162,40 @@
return imageCapture
}
+
+ private fun createMediaStoreOutputOptions(): ImageCapture.OutputFileOptions {
+ // Create time stamped name and MediaStore entry.
+ val name =
+ FILENAME_PREFIX +
+ SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US)
+ .format(System.currentTimeMillis())
+ val contentValues =
+ ContentValues().apply {
+ put(MediaStore.MediaColumns.DISPLAY_NAME, name)
+ put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
+ put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
+ }
+ }
+
+ // Create output options object which contains file + metadata
+ return ImageCapture.OutputFileOptions.Builder(
+ context.contentResolver,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ contentValues
+ )
+ .build()
+ }
+
+ companion object {
+ private const val FILENAME_PREFIX = "cameraXPhoto"
+
+ @JvmStatic
+ @ParameterizedRobolectricTestRunner.Parameters(name = "LensFacing = {0}")
+ fun data() =
+ listOf(
+ arrayOf(CameraSelector.LENS_FACING_BACK),
+ arrayOf(CameraSelector.LENS_FACING_FRONT),
+ )
+ }
}
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
index 331ed44..b7f221e 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
@@ -29,6 +29,7 @@
import androidx.camera.testing.impl.CameraPipeConfigTestRule
import androidx.camera.testing.impl.CameraUtil
import androidx.camera.testing.impl.CoreAppTestUtil
+import androidx.camera.testing.impl.InternalTestConvenience.useInCameraTest
import androidx.camera.view.PreviewView
import androidx.lifecycle.Lifecycle.State
import androidx.test.core.app.ActivityScenario
@@ -135,7 +136,7 @@
// The test makes sure the camera PreviewView is in the streaming state.
@Test
fun testPreviewViewUpdateAfterStopResume() {
- launchActivity(lensFacing, cameraXConfig).use { scenario ->
+ launchActivity(lensFacing, cameraXConfig).useInCameraTest { scenario ->
// At first, check Preview in stream state
assertStreamState(scenario, PreviewView.StreamState.STREAMING)
@@ -152,7 +153,7 @@
// The test makes sure the TextureView surface texture keeps the same after switch.
@Test
fun testPreviewViewUpdateAfterSwitch() {
- launchActivity(lensFacing, cameraXConfig).use { scenario ->
+ launchActivity(lensFacing, cameraXConfig).useInCameraTest { scenario ->
// At first, check Preview in stream state
assertStreamState(scenario, PreviewView.StreamState.STREAMING)
@@ -175,7 +176,7 @@
)
@Test
fun testPreviewViewUpdateAfterSwitchOutAndStop_ResumeAndSwitchBack() {
- launchActivity(lensFacing, cameraXConfig).use { scenario ->
+ launchActivity(lensFacing, cameraXConfig).useInCameraTest { scenario ->
// At first, check Preview in stream state
assertStreamState(scenario, PreviewView.StreamState.STREAMING)
diff --git a/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderScreenshotTest.kt b/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderScreenshotTest.kt
index f38064c..9e6c1f6 100644
--- a/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderScreenshotTest.kt
+++ b/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderScreenshotTest.kt
@@ -20,16 +20,20 @@
import androidx.camera.testing.impl.SurfaceUtil
import androidx.camera.viewfinder.surface.ImplementationMode
import androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest
+import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.outlined.Face
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.testutils.assertAgainstGolden
import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas as ComposeCanvas
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.graphics.nativeCanvas
@@ -47,6 +51,7 @@
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.test.screenshot.AndroidXScreenshotTestRule
+import androidx.test.screenshot.matchers.MSSIMMatcher
import kotlin.math.abs
import kotlinx.coroutines.runBlocking
import org.junit.Ignore
@@ -107,6 +112,160 @@
assertImplementationDrawsUpright(testParams)
}
+ @Test
+ fun embeddedImplementationDrawsUpright_fromHorizontallyMirroredSource() = runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 0,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredHorizontally = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
+ @Test
+ fun embeddedImplementationDrawsUpright_from90Degree_HorizontallyMirroredSource() = runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 90,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredHorizontally = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
+ @Test
+ fun embeddedImplementationDrawsUpright_from180Degree_HorizontallyMirroredSource() =
+ runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 180,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredHorizontally = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
+ @Test
+ fun embeddedImplementationDrawsUpright_from270Degree_HorizontallyMirroredSource() =
+ runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 270,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredHorizontally = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
+ @Test
+ fun embeddedImplementationDrawsUpright_fromVerticallyMirroredSource() = runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 0,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredVertically = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
+ @Test
+ fun embeddedImplementationDrawsUpright_from90Degree_VerticallyMirroredSource() = runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 90,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredVertically = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
+ @Test
+ fun embeddedImplementationDrawsUpright_from180Degree_VerticallyMirroredSource() = runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 180,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredVertically = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
+ @Test
+ fun embeddedImplementationDrawsUpright_from270Degree_VerticallyMirroredSource() = runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 270,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredVertically = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
+ @Test
+ fun embeddedImplementationDrawsUpright_fromVerticallyAndHorizontallyMirroredSource() =
+ runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 0,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredHorizontally = true,
+ isMirroredVertically = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
+ @Test
+ fun embeddedImplementationDrawsUpright_from90Degree_VerticallyAndHorizontallyMirroredSource() =
+ runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 90,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredHorizontally = true,
+ isMirroredVertically = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
+ @Test
+ fun embeddedImplementationDrawsUpright_from180Degree_VerticallyAndHorizontallyMirroredSource() =
+ runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 180,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredHorizontally = true,
+ isMirroredVertically = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
+ @Test
+ fun embeddedImplementationDrawsUpright_from270Degree_VerticallyAndHorizontallyMirroredSource() =
+ runBlocking {
+ val testParams =
+ ViewfinderTestParams(
+ sourceRotation = 270,
+ implementationMode = ImplementationMode.EMBEDDED,
+ isMirroredHorizontally = true,
+ isMirroredVertically = true
+ )
+
+ assertImplementationDrawsUpright(testParams)
+ }
+
@Ignore("b/338466761")
@Test
fun externalImplementationDrawsUpright_from0DegreeSource() = runBlocking {
@@ -157,35 +316,83 @@
private fun assertImplementationDrawsUpright(testParams: ViewfinderTestParams) {
val surfaceRequest = ViewfinderSurfaceRequest.Builder(testParams.sourceResolution).build()
+ val coordinateTransformer = MutableCoordinateTransformer()
composeTestRule.setContent {
Viewfinder(
modifier = Modifier.size(testParams.viewfinderSize).testTag(VIEWFINDER_TAG),
surfaceRequest = surfaceRequest,
transformationInfo = testParams.transformationInfo,
- implementationMode = testParams.implementationMode
+ implementationMode = testParams.implementationMode,
+ coordinateTransformer = coordinateTransformer
)
- DrawFaceToSurface(testParams = testParams, surfaceRequest = surfaceRequest)
+ val touchCoordinates = Offset(200f, 200f)
+
+ // Draw touch coordinate on top of Viewfinder
+ val imageVec = Icons.Filled.Add
+ val painter = rememberVectorPainter(image = imageVec)
+ val density = LocalDensity.current
+ Canvas(modifier = Modifier.size(testParams.viewfinderSize)) {
+ val imageSize =
+ with(density) {
+ Size(imageVec.defaultWidth.toPx(), imageVec.defaultHeight.toPx())
+ }
+ withTransform({
+ translate(
+ left = touchCoordinates.x - imageSize.width / 2f,
+ top = touchCoordinates.y - imageSize.height / 2f
+ )
+ }) {
+ with(painter) {
+ draw(size = imageSize, colorFilter = ColorFilter.tint(Color.Green))
+ }
+ }
+ }
+
+ // Fill Viewfinder buffer with content
+ DrawFaceToSurface(
+ testParams = testParams,
+ surfaceRequest = surfaceRequest,
+ coordinateTransformer = coordinateTransformer,
+ touchCoordinates = touchCoordinates
+ )
}
composeTestRule
.onNodeWithTag(VIEWFINDER_TAG)
.captureToImage()
- .assertAgainstGolden(screenshotRule, "upright_face")
+ .assertAgainstGolden(
+ rule = screenshotRule,
+ goldenIdentifier = "upright_face_with_mapped_touch_point",
+ // Tuned to find a 1px difference in mapped touch coordinates.
+ // May need to split out touch coordinate mapping into its own
+ // screenshot test if this becomes flaky.
+ matcher = MSSIMMatcher(threshold = 0.9995)
+ )
}
+ /** This emulates the camera sensor. */
@RequiresApi(26)
@Composable
private fun DrawFaceToSurface(
testParams: ViewfinderTestParams,
- surfaceRequest: ViewfinderSurfaceRequest
+ surfaceRequest: ViewfinderSurfaceRequest,
+ coordinateTransformer: CoordinateTransformer,
+ touchCoordinates: Offset?
) {
val imageVec = Icons.Outlined.Face
val painter = rememberVectorPainter(image = imageVec)
val density = LocalDensity.current
LaunchedEffect(Unit) {
val surface = surfaceRequest.getSurface()
- SurfaceUtil.setBuffersTransform(surface, testParams.sourceRotation.toTransformEnum())
+ SurfaceUtil.setBuffersTransform(
+ surface,
+ toTransformEnum(
+ sourceRotation = testParams.sourceRotation,
+ horizontalMirror = testParams.isMirroredHorizontally,
+ verticalMirror = testParams.isMirroredVertically
+ )
+ )
val resolution = testParams.sourceResolution
val canvas = ComposeCanvas(surface.lockHardwareCanvas())
try {
@@ -197,8 +404,24 @@
) {
val rotation = testParams.sourceRotation
val iconSize = imageVec.calcFitSize(size, rotation, density)
+ val mirrorX =
+ when (testParams.isMirroredHorizontally) {
+ true -> -1.0f
+ false -> 1.0f
+ }
+ val flipY =
+ when (testParams.isMirroredVertically) {
+ true -> -1.0f
+ false -> 1.0f
+ }
+
drawRect(Color.Gray)
+
+ // For drawing the face, we need to emulate how the real world
+ // would project onto the sensor. So we must apply the reverse rotation
+ // and mirroring.
withTransform({
+ scale(mirrorX, flipY)
rotate(degrees = -rotation.toFloat())
translate(
left = (size.width - iconSize.width) / 2f,
@@ -207,6 +430,18 @@
}) {
with(painter) { draw(iconSize) }
}
+
+ // For drawing the touch coordinates, we are already in the "sensor"
+ // coordinates. No need to apply any transformations.
+ touchCoordinates?.let {
+ with(coordinateTransformer) {
+ drawCircle(
+ radius = 25f,
+ color = Color.Red,
+ center = touchCoordinates.transform()
+ )
+ }
+ }
}
} finally {
surface.unlockCanvasAndPost(canvas.nativeCanvas)
@@ -231,17 +466,36 @@
private fun Size.swapDimens(): Size = Size(height, width)
- private fun Int.toTransformEnum(): Int {
- return when (this) {
- 0 -> SurfaceUtil.TRANSFORM_IDENTITY
- 90 -> SurfaceUtil.TRANSFORM_ROTATE_90
- 180 -> SurfaceUtil.TRANSFORM_ROTATE_180
- 270 -> SurfaceUtil.TRANSFORM_ROTATE_270
- else ->
- throw IllegalArgumentException(
- "Rotation value $this does not correspond to valid transform"
- )
- }
+ private fun toTransformEnum(
+ sourceRotation: Int,
+ horizontalMirror: Boolean,
+ verticalMirror: Boolean
+ ): Int {
+ val rotationTransform =
+ when (sourceRotation) {
+ 0 -> SurfaceUtil.TRANSFORM_IDENTITY
+ 90 -> SurfaceUtil.TRANSFORM_ROTATE_90
+ 180 -> SurfaceUtil.TRANSFORM_ROTATE_180
+ 270 -> SurfaceUtil.TRANSFORM_ROTATE_270
+ else ->
+ throw IllegalArgumentException(
+ "Rotation value $this does not correspond to valid transform"
+ )
+ }
+
+ val horizontalMirrorTransform =
+ when (horizontalMirror) {
+ true -> SurfaceUtil.TRANSFORM_MIRROR_HORIZONTAL
+ false -> SurfaceUtil.TRANSFORM_IDENTITY
+ }
+
+ val verticalMirrorTransform =
+ when (verticalMirror) {
+ true -> SurfaceUtil.TRANSFORM_MIRROR_VERTICAL
+ false -> SurfaceUtil.TRANSFORM_IDENTITY
+ }
+
+ return (horizontalMirrorTransform or verticalMirrorTransform) xor rotationTransform
}
}
diff --git a/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTest.kt b/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTest.kt
index 9a165ac..3feb673 100644
--- a/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTest.kt
+++ b/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTest.kt
@@ -108,11 +108,12 @@
transformationInfo =
TransformationInfo(
sourceRotation = 0,
+ isSourceMirroredHorizontally = false,
+ isSourceMirroredVertically = false,
cropRectLeft = 0,
- cropRectRight = 270,
cropRectTop = 0,
- cropRectBottom = 480,
- shouldMirror = false
+ cropRectRight = 270,
+ cropRectBottom = 480
),
implementationMode = ImplementationMode.EXTERNAL,
coordinateTransformer = coordinateTransformer
diff --git a/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTestParams.kt b/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTestParams.kt
index b99b9ea..a41aa5d 100644
--- a/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTestParams.kt
+++ b/camera/viewfinder/viewfinder-compose/src/androidTest/kotlin/androidx/camera/viewfinder/compose/ViewfinderTestParams.kt
@@ -34,14 +34,17 @@
else -> throw IllegalArgumentException("Invalid source rotation: $sourceRotation")
},
val implementationMode: ImplementationMode = ImplementationMode.EXTERNAL,
+ val isMirroredHorizontally: Boolean = false,
+ val isMirroredVertically: Boolean = false,
val transformationInfo: TransformationInfo =
TransformationInfo(
sourceRotation = sourceRotation,
+ isSourceMirroredHorizontally = isMirroredHorizontally,
+ isSourceMirroredVertically = isMirroredVertically,
cropRectLeft = 0,
cropRectTop = 0,
cropRectRight = sourceResolution.width,
cropRectBottom = sourceResolution.height,
- shouldMirror = false
)
) {
companion object {
diff --git a/camera/viewfinder/viewfinder-compose/src/main/java/androidx/camera/viewfinder/androidx-camera-viewfinder-viewfinder-compose-documentation.md b/camera/viewfinder/viewfinder-compose/src/main/java/androidx/camera/viewfinder/androidx-camera-viewfinder-viewfinder-compose-documentation.md
index 10d4659..18eb197 100644
--- a/camera/viewfinder/viewfinder-compose/src/main/java/androidx/camera/viewfinder/androidx-camera-viewfinder-viewfinder-compose-documentation.md
+++ b/camera/viewfinder/viewfinder-compose/src/main/java/androidx/camera/viewfinder/androidx-camera-viewfinder-viewfinder-compose-documentation.md
@@ -1,7 +1,7 @@
# Module root
-CameraX ViewFinder Compose
+Camera Viewfinder Compose
# Package androidx.camera.viewfinder.compose
-Library providing a composable ViewFinder
+Standalone Composable Viewfinder for Camera
diff --git a/camera/viewfinder/viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt b/camera/viewfinder/viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt
index 3efcc08..bda4100 100644
--- a/camera/viewfinder/viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt
+++ b/camera/viewfinder/viewfinder-compose/src/main/java/androidx/camera/viewfinder/compose/internal/SurfaceTransformationUtil.kt
@@ -124,20 +124,12 @@
transformationInfo.sourceRotation
)
- if (transformationInfo.shouldMirror) {
- if (TransformUtil.is90or270(transformationInfo.sourceRotation)) {
- // If the rotation is 90/270, the Surface should be flipped vertically.
- // +---+ 90 +---+ 270 +---+
- // | ^ | --> | < | | > |
- // +---+ +---+ +---+
- matrix.preScale(1f, -1f, surfaceCropRect.centerX(), surfaceCropRect.centerY())
- } else {
- // If the rotation is 0/180, the Surface should be flipped horizontally.
- // +---+ 0 +---+ 180 +---+
- // | ^ | --> | ^ | | v |
- // +---+ +---+ +---+
- matrix.preScale(-1f, 1f, surfaceCropRect.centerX(), surfaceCropRect.centerY())
- }
+ if (transformationInfo.isSourceMirroredHorizontally) {
+ matrix.preScale(-1f, 1f, surfaceCropRect.centerX(), surfaceCropRect.centerY())
+ }
+
+ if (transformationInfo.isSourceMirroredVertically) {
+ matrix.preScale(1f, -1f, surfaceCropRect.centerX(), surfaceCropRect.centerY())
}
return matrix
}
diff --git a/camera/viewfinder/viewfinder-core/api/current.txt b/camera/viewfinder/viewfinder-core/api/current.txt
index 31ea519..b9da3f9 100644
--- a/camera/viewfinder/viewfinder-core/api/current.txt
+++ b/camera/viewfinder/viewfinder-core/api/current.txt
@@ -63,18 +63,20 @@
}
public final class TransformationInfo {
- ctor public TransformationInfo(int sourceRotation, int cropRectLeft, int cropRectTop, int cropRectRight, int cropRectBottom, boolean shouldMirror);
+ ctor public TransformationInfo(int sourceRotation, boolean isSourceMirroredHorizontally, boolean isSourceMirroredVertically, int cropRectLeft, int cropRectTop, int cropRectRight, int cropRectBottom);
method public int getCropRectBottom();
method public int getCropRectLeft();
method public int getCropRectRight();
method public int getCropRectTop();
method public int getSourceRotation();
- method public boolean shouldMirror();
+ method public boolean isSourceMirroredHorizontally();
+ method public boolean isSourceMirroredVertically();
property public final int cropRectBottom;
property public final int cropRectLeft;
property public final int cropRectRight;
property public final int cropRectTop;
- property public final boolean shouldMirror;
+ property public final boolean isSourceMirroredHorizontally;
+ property public final boolean isSourceMirroredVertically;
property public final int sourceRotation;
}
diff --git a/camera/viewfinder/viewfinder-core/api/restricted_current.txt b/camera/viewfinder/viewfinder-core/api/restricted_current.txt
index 31ea519..b9da3f9 100644
--- a/camera/viewfinder/viewfinder-core/api/restricted_current.txt
+++ b/camera/viewfinder/viewfinder-core/api/restricted_current.txt
@@ -63,18 +63,20 @@
}
public final class TransformationInfo {
- ctor public TransformationInfo(int sourceRotation, int cropRectLeft, int cropRectTop, int cropRectRight, int cropRectBottom, boolean shouldMirror);
+ ctor public TransformationInfo(int sourceRotation, boolean isSourceMirroredHorizontally, boolean isSourceMirroredVertically, int cropRectLeft, int cropRectTop, int cropRectRight, int cropRectBottom);
method public int getCropRectBottom();
method public int getCropRectLeft();
method public int getCropRectRight();
method public int getCropRectTop();
method public int getSourceRotation();
- method public boolean shouldMirror();
+ method public boolean isSourceMirroredHorizontally();
+ method public boolean isSourceMirroredVertically();
property public final int cropRectBottom;
property public final int cropRectLeft;
property public final int cropRectRight;
property public final int cropRectTop;
- property public final boolean shouldMirror;
+ property public final boolean isSourceMirroredHorizontally;
+ property public final boolean isSourceMirroredVertically;
property public final int sourceRotation;
}
diff --git a/camera/viewfinder/viewfinder-core/src/main/java/androidx/camera/viewfinder/androidx-camera-viewfinder-viewfinder-core-documentation.md b/camera/viewfinder/viewfinder-core/src/main/java/androidx/camera/viewfinder/androidx-camera-viewfinder-viewfinder-core-documentation.md
index fe8fd60..6f4318c 100644
--- a/camera/viewfinder/viewfinder-core/src/main/java/androidx/camera/viewfinder/androidx-camera-viewfinder-viewfinder-core-documentation.md
+++ b/camera/viewfinder/viewfinder-core/src/main/java/androidx/camera/viewfinder/androidx-camera-viewfinder-viewfinder-core-documentation.md
@@ -1,7 +1,7 @@
# Module root
-CameraX ViewFinder Core
+Camera Viewfinder Core
# Package androidx.camera.viewfinder
-Library providing core dependencies for ViewFinder
+Core dependencies for Viewfinder
diff --git a/camera/viewfinder/viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/TransformationInfo.kt b/camera/viewfinder/viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/TransformationInfo.kt
index fae6085..b95b7be 100644
--- a/camera/viewfinder/viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/TransformationInfo.kt
+++ b/camera/viewfinder/viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/TransformationInfo.kt
@@ -19,7 +19,7 @@
/**
* Transformation information associated with the preview output.
*
- * This information can be used to transform the Surface of a ViewFinder to be suitable to be
+ * This information can be used to transform the Surface of a Viewfinder to be suitable to be
* displayed.
*/
class TransformationInfo(
@@ -27,6 +27,35 @@
/** Rotation of the source, relative to the device's natural rotation. */
val sourceRotation: Int,
+ /**
+ * Indicates whether the source has been mirrored horizontally.
+ *
+ * This is common if the source comes from a camera that is front-facing.
+ *
+ * It is not common for both [isSourceMirroredHorizontally] and [isSourceMirroredVertically] to
+ * be set to `true`. This is equivalent to [sourceRotation] being rotated by an additional 180
+ * degrees.
+ *
+ * @see android.hardware.camera2.params.OutputConfiguration.MIRROR_MODE_AUTO
+ * @see android.hardware.camera2.params.OutputConfiguration.MIRROR_MODE_H
+ * @see androidx.camera.core.SurfaceRequest.TransformationInfo.isMirroring
+ */
+ val isSourceMirroredHorizontally: Boolean,
+
+ /**
+ * Indicates whether the source has been mirrored vertically.
+ *
+ * It is not common for a camera source to be mirror vertically, and typically
+ * [isSourceMirroredHorizontally] will be the appropriate property.
+ *
+ * It is not common for both [isSourceMirroredHorizontally] and [isSourceMirroredVertically] to
+ * be set to `true`. This is equivalent to [sourceRotation] being rotated by an additional 180
+ * degrees.
+ *
+ * @see android.hardware.camera2.params.OutputConfiguration.MIRROR_MODE_V
+ */
+ val isSourceMirroredVertically: Boolean,
+
/** Left offset of the cropRect in pixels */
val cropRectLeft: Int,
@@ -38,29 +67,30 @@
/** Bottom offset of the cropRect in pixels */
val cropRectBottom: Int,
- @get:JvmName("shouldMirror") val shouldMirror: Boolean,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is TransformationInfo) return false
if (sourceRotation != other.sourceRotation) return false
+ if (isSourceMirroredHorizontally != other.isSourceMirroredHorizontally) return false
+ if (isSourceMirroredVertically != other.isSourceMirroredVertically) return false
if (cropRectLeft != other.cropRectLeft) return false
if (cropRectTop != other.cropRectTop) return false
if (cropRectRight != other.cropRectRight) return false
if (cropRectBottom != other.cropRectBottom) return false
- if (shouldMirror != other.shouldMirror) return false
return true
}
override fun hashCode(): Int {
var result = sourceRotation
+ result = 31 * result + isSourceMirroredHorizontally.hashCode()
+ result = 31 * result + isSourceMirroredVertically.hashCode()
result = 31 * result + cropRectLeft
result = 31 * result + cropRectTop
result = 31 * result + cropRectRight
result = 31 * result + cropRectBottom
- result = 31 * result + shouldMirror.hashCode()
return result
}
}
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
index 2959671..1f975fd2 100644
--- a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
@@ -32,8 +32,6 @@
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.CarText;
import androidx.car.app.model.Item;
-import androidx.car.app.model.Row;
-import androidx.car.app.model.Template;
import androidx.car.app.model.constraints.ActionsConstraints;
import androidx.car.app.utils.CollectionUtils;
import androidx.core.app.Person;
@@ -199,17 +197,9 @@
}
/**
- * Returns whether this item should be included in an indexed list.
+ * Returns whether this item can be included in indexed lists.
*
- * <p>"Indexing" refers to the process of examining list contents (e.g. item titles) to sort,
- * partition, or filter a list. Indexing is generally used for features called "Accelerators",
- * which allow a user to quickly find a particular {@link Item} in a long list.
- *
- * <p>To exclude a single item from indexed lists and accelerator features, use
- * {@link Row.Builder#setIndexable(boolean)}.
- *
- * <p>To enable/disable accelerators for the entire list, see the API for the particular
- * list-like {@link Template} that you are using.
+ * @see Builder#setIndexable(boolean)
*/
@ExperimentalCarApi
public boolean isIndexable() {
@@ -343,7 +333,26 @@
return this;
}
- /** @see #isIndexable */
+ /**
+ * Sets whether this item can be included in indexed lists. By default, this is set to
+ * {@code true}.
+ *
+ * <p>The host creates indexed lists to help users navigate through long lists more easily
+ * by sorting, filtering, or some other means.
+ *
+ * <p>For example, a media app may, by default, show a user's playlists sorted by date
+ * created. If the app provides these playlists via the {@code SectionedItemTemplate} and
+ * enables {@code #isAlphabeticalIndexingAllowed}, the user will be able to jump to their
+ * playlists that start with the letter "H". When this happens, the list is reconstructed
+ * and sorted alphabetically, then shown to the user, jumping down to the letter "H". If
+ * the item is set to {@code #setIndexable(false)}, the item will not show up in this newly
+ * sorted list.
+ *
+ * <p>Individual items can be set to be included or excluded from filtered lists, but it's
+ * also possible to enable/disable the creation of filtered lists as a whole via the
+ * template's API (eg. {@code SectionedItemTemplate
+ * .Builder#setAlphabeticalIndexingAllowed(Boolean)}).
+ */
@ExperimentalCarApi
@NonNull
public Builder setIndexable(boolean indexable) {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/GridItem.java b/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
index 0645006..7e1e705 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
@@ -157,17 +157,9 @@
}
/**
- * Returns whether this item should be included in an indexed list.
+ * Returns whether this item can be included in indexed lists.
*
- * <p>"Indexing" refers to the process of examining list contents (e.g. item titles) to sort,
- * partition, or filter a list. Indexing is generally used for features called "Accelerators",
- * which allow a user to quickly find a particular {@link Item} in a long list.
- *
- * <p>To exclude a single item from indexed lists and accelerator features, use
- * {@link Row.Builder#setIndexable(boolean)}.
- *
- * <p>To enable/disable accelerators for the entire list, see the API for the particular
- * list-like {@link Template} that you are using.
+ * @see Builder#setIndexable(boolean)
*/
@ExperimentalCarApi
public boolean isIndexable() {
@@ -261,7 +253,7 @@
boolean mIsLoading;
@Nullable
Badge mBadge;
- boolean mIndexable;
+ boolean mIndexable = true;
/**
* Sets whether the item is in a loading state.
@@ -454,7 +446,27 @@
return this;
}
- /** @see #isIndexable */
+ /**
+ * Sets whether this item can be included in indexed lists. By default, this is set to
+ * {@code true}.
+ *
+ * <p>The host creates indexed lists to help users navigate through long lists more easily
+ * by sorting, filtering, or some other means.
+ *
+ * <p>For example, a media app may, by default, show a user's playlists sorted by date
+ * created. If the app provides these playlists via the {@code SectionedItemTemplate} and
+ * enables {@code #isAlphabeticalIndexingAllowed}, the user will be able to select a letter
+ * on a keyboard to jump to their playlists that start with that letter. When this happens,
+ * the list is reconstructed and sorted alphabetically, then shown to the user, jumping down
+ * to the letter. Items that are set to {@code #setIndexable(false)}, do not show up in this
+ * new sorted list. Sticking with the media example, a media app may choose to hide things
+ * like "autogenerated playlists" from the list and only keep user created playlists.
+ *
+ * <p>Individual items can be set to be included or excluded from filtered lists, but it's
+ * also possible to enable/disable the creation of filtered lists as a whole via the
+ * template's API (eg. {@code SectionedItemTemplate
+ * .Builder#setAlphabeticalIndexingAllowed(Boolean)}).
+ */
@ExperimentalCarApi
@NonNull
public Builder setIndexable(boolean indexable) {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Row.java b/car/app/app/src/main/java/androidx/car/app/model/Row.java
index 46835b3..1ab8b4f 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Row.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Row.java
@@ -249,17 +249,9 @@
}
/**
- * Returns whether this item should be included in an indexed list.
+ * Returns whether this item can be included in indexed lists.
*
- * <p>"Indexing" refers to the process of examining list contents (e.g. item titles) to sort,
- * partition, or filter a list. Indexing is generally used for features called "Accelerators",
- * which allow a user to quickly find a particular {@link Item} in a long list.
- *
- * <p>To exclude a single item from indexed lists and accelerator features, use
- * {@link Row.Builder#setIndexable(boolean)}.
- *
- * <p>To enable/disable accelerators for the entire list, see the API for the particular
- * list-like {@link Template} that you are using.
+ * @see Builder#setIndexable(boolean)
*/
@ExperimentalCarApi
public boolean isIndexable() {
@@ -694,7 +686,27 @@
return this;
}
- /** @see #isIndexable */
+ /**
+ * Sets whether this item can be included in indexed lists. By default, this is set to
+ * {@code true}.
+ *
+ * <p>The host creates indexed lists to help users navigate through long lists more easily
+ * by sorting, filtering, or some other means.
+ *
+ * <p>For example, a media app may, by default, show a user's playlists sorted by date
+ * created. If the app provides these playlists via the {@code SectionedItemTemplate} and
+ * enables {@code #isAlphabeticalIndexingAllowed}, the user will be able to select a letter
+ * on a keyboard to jump to their playlists that start with that letter. When this happens,
+ * the list is reconstructed and sorted alphabetically, then shown to the user, jumping down
+ * to the letter. Items that are set to {@code #setIndexable(false)}, do not show up in this
+ * new sorted list. Sticking with the media example, a media app may choose to hide things
+ * like "autogenerated playlists" from the list and only keep user created playlists.
+ *
+ * <p>Individual items can be set to be included or excluded from filtered lists, but it's
+ * also possible to enable/disable the creation of filtered lists as a whole via the
+ * template's API (eg. {@code SectionedItemTemplate
+ * .Builder#setAlphabeticalIndexingAllowed(Boolean)}).
+ */
@ExperimentalCarApi
@NonNull
public Builder setIndexable(boolean indexable) {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/SectionedItemTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/SectionedItemTemplate.java
index 9f1b19c0..694b82d 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/SectionedItemTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/SectionedItemTemplate.java
@@ -262,7 +262,7 @@
/**
* Sets whether or not this template is in a loading state. If passed {@code true}, sections
- * cannot be added to the template.
+ * cannot be added to the template. By default, this is {@code false}.
*/
@NonNull
@CanIgnoreReturnValue
@@ -272,15 +272,22 @@
}
/**
- * Sets whether this list can be indexed alphabetically, by item title.
+ * Sets whether this list can be indexed alphabetically, by item title. By default, this
+ * is {@code false}.
*
* <p>"Indexing" refers to the process of examining list contents (e.g. item titles) to
- * sort,
- * partition, or filter a list. Indexing is generally used for features called
- * "Accelerators",
- * which allow a user to quickly find a particular {@link Item} in a long list.
+ * sort, partition, or filter a list. Indexing is generally used for features called
+ * "Accelerators", which allow a user to quickly find a particular {@link Item} in a long
+ * list.
*
- * <p>To exclude a single item from indexing, see the relevant item's API.
+ * <p>For example, a media app may, by default, show a user's playlists sorted by date
+ * created. If the app provides these playlists via the {@code SectionedItemTemplate} and
+ * enables {@link #isAlphabeticalIndexingAllowed}, the user will be able to jump to their
+ * playlists that start with the letter "H". When this happens, the list is reconstructed
+ * and sorted alphabetically, then shown to the user, jumping down to the letter "H".
+ *
+ * <p>Individual items may be excluded from the list by setting their {@code #isIndexable}
+ * field to {@code false}.
*/
@NonNull
@CanIgnoreReturnValue
diff --git a/collection/collection/api/current.txt b/collection/collection/api/current.txt
index 40c5ca2..688cf79 100644
--- a/collection/collection/api/current.txt
+++ b/collection/collection/api/current.txt
@@ -95,11 +95,14 @@
}
public abstract sealed class DoubleList {
- method public final boolean any();
+ method public final inline boolean any();
method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
+ method public final int binarySearch(int element);
+ method public final int binarySearch(int element, optional int fromIndex);
+ method public final int binarySearch(int element, optional int fromIndex, optional int toIndex);
method public final operator boolean contains(double element);
method public final boolean containsAll(androidx.collection.DoubleList elements);
- method public final int count();
+ method public final inline int count();
method public final inline int count(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
method public final double elementAt(@IntRange(from=0L) int index);
method public final inline double elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Double> defaultValue);
@@ -116,12 +119,12 @@
method public final operator double get(@IntRange(from=0L) int index);
method public final inline kotlin.ranges.IntRange getIndices();
method @IntRange(from=-1L) public final inline int getLastIndex();
- method @IntRange(from=0L) public final int getSize();
+ method @IntRange(from=0L) public final inline int getSize();
method public final int indexOf(double element);
method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
- method public final boolean isEmpty();
- method public final boolean isNotEmpty();
+ method public final inline boolean isEmpty();
+ method public final inline boolean isNotEmpty();
method public final String joinToString();
method public final String joinToString(optional CharSequence separator);
method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
@@ -137,11 +140,11 @@
method public final double last();
method public final inline double last(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
method public final int lastIndexOf(double element);
- method public final boolean none();
+ method public final inline boolean none();
method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
property public final inline kotlin.ranges.IntRange indices;
property @IntRange(from=-1L) public final inline int lastIndex;
- property @IntRange(from=0L) public final int size;
+ property @IntRange(from=0L) public final inline int size;
}
public final class DoubleListKt {
@@ -274,11 +277,14 @@
}
public abstract sealed class FloatList {
- method public final boolean any();
+ method public final inline boolean any();
method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
+ method public final int binarySearch(int element);
+ method public final int binarySearch(int element, optional int fromIndex);
+ method public final int binarySearch(int element, optional int fromIndex, optional int toIndex);
method public final operator boolean contains(float element);
method public final boolean containsAll(androidx.collection.FloatList elements);
- method public final int count();
+ method public final inline int count();
method public final inline int count(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
method public final float elementAt(@IntRange(from=0L) int index);
method public final inline float elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Float> defaultValue);
@@ -295,12 +301,12 @@
method public final operator float get(@IntRange(from=0L) int index);
method public final inline kotlin.ranges.IntRange getIndices();
method @IntRange(from=-1L) public final inline int getLastIndex();
- method @IntRange(from=0L) public final int getSize();
+ method @IntRange(from=0L) public final inline int getSize();
method public final int indexOf(float element);
method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
- method public final boolean isEmpty();
- method public final boolean isNotEmpty();
+ method public final inline boolean isEmpty();
+ method public final inline boolean isNotEmpty();
method public final String joinToString();
method public final String joinToString(optional CharSequence separator);
method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
@@ -316,11 +322,11 @@
method public final float last();
method public final inline float last(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
method public final int lastIndexOf(float element);
- method public final boolean none();
+ method public final inline boolean none();
method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
property public final inline kotlin.ranges.IntRange indices;
property @IntRange(from=-1L) public final inline int lastIndex;
- property @IntRange(from=0L) public final int size;
+ property @IntRange(from=0L) public final inline int size;
}
public final class FloatListKt {
@@ -602,11 +608,14 @@
}
public abstract sealed class IntList {
- method public final boolean any();
+ method public final inline boolean any();
method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
+ method public final int binarySearch(int element);
+ method public final int binarySearch(int element, optional int fromIndex);
+ method public final int binarySearch(int element, optional int fromIndex, optional int toIndex);
method public final operator boolean contains(int element);
method public final boolean containsAll(androidx.collection.IntList elements);
- method public final int count();
+ method public final inline int count();
method public final inline int count(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
method public final int elementAt(@IntRange(from=0L) int index);
method public final inline int elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> defaultValue);
@@ -623,12 +632,12 @@
method public final operator int get(@IntRange(from=0L) int index);
method public final inline kotlin.ranges.IntRange getIndices();
method @IntRange(from=-1L) public final inline int getLastIndex();
- method @IntRange(from=0L) public final int getSize();
+ method @IntRange(from=0L) public final inline int getSize();
method public final int indexOf(int element);
method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
- method public final boolean isEmpty();
- method public final boolean isNotEmpty();
+ method public final inline boolean isEmpty();
+ method public final inline boolean isNotEmpty();
method public final String joinToString();
method public final String joinToString(optional CharSequence separator);
method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
@@ -644,11 +653,11 @@
method public final int last();
method public final inline int last(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
method public final int lastIndexOf(int element);
- method public final boolean none();
+ method public final inline boolean none();
method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
property public final inline kotlin.ranges.IntRange indices;
property @IntRange(from=-1L) public final inline int lastIndex;
- property @IntRange(from=0L) public final int size;
+ property @IntRange(from=0L) public final inline int size;
}
public final class IntListKt {
@@ -919,11 +928,14 @@
}
public abstract sealed class LongList {
- method public final boolean any();
+ method public final inline boolean any();
method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
+ method public final int binarySearch(int element);
+ method public final int binarySearch(int element, optional int fromIndex);
+ method public final int binarySearch(int element, optional int fromIndex, optional int toIndex);
method public final operator boolean contains(long element);
method public final boolean containsAll(androidx.collection.LongList elements);
- method public final int count();
+ method public final inline int count();
method public final inline int count(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
method public final long elementAt(@IntRange(from=0L) int index);
method public final inline long elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Long> defaultValue);
@@ -940,12 +952,12 @@
method public final operator long get(@IntRange(from=0L) int index);
method public final inline kotlin.ranges.IntRange getIndices();
method @IntRange(from=-1L) public final inline int getLastIndex();
- method @IntRange(from=0L) public final int getSize();
+ method @IntRange(from=0L) public final inline int getSize();
method public final int indexOf(long element);
method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
- method public final boolean isEmpty();
- method public final boolean isNotEmpty();
+ method public final inline boolean isEmpty();
+ method public final inline boolean isNotEmpty();
method public final String joinToString();
method public final String joinToString(optional CharSequence separator);
method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
@@ -961,11 +973,11 @@
method public final long last();
method public final inline long last(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
method public final int lastIndexOf(long element);
- method public final boolean none();
+ method public final inline boolean none();
method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
property public final inline kotlin.ranges.IntRange indices;
property @IntRange(from=-1L) public final inline int lastIndex;
- property @IntRange(from=0L) public final int size;
+ property @IntRange(from=0L) public final inline int size;
}
public final class LongListKt {
@@ -1213,8 +1225,8 @@
ctor public MutableDoubleList(optional int initialCapacity);
method public boolean add(double element);
method public void add(@IntRange(from=0L) int index, double element);
- method public boolean addAll(androidx.collection.DoubleList elements);
- method public boolean addAll(double[] elements);
+ method public inline boolean addAll(androidx.collection.DoubleList elements);
+ method public inline boolean addAll(double[] elements);
method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.DoubleList elements);
method public boolean addAll(@IntRange(from=0L) int index, double[] elements);
method public void clear();
@@ -1223,9 +1235,9 @@
method public operator void minusAssign(androidx.collection.DoubleList elements);
method public inline operator void minusAssign(double element);
method public operator void minusAssign(double[] elements);
- method public operator void plusAssign(androidx.collection.DoubleList elements);
+ method public inline operator void plusAssign(androidx.collection.DoubleList elements);
method public inline operator void plusAssign(double element);
- method public operator void plusAssign(double[] elements);
+ method public inline operator void plusAssign(double[] elements);
method public boolean remove(double element);
method public boolean removeAll(androidx.collection.DoubleList elements);
method public boolean removeAll(double[] elements);
@@ -1285,8 +1297,8 @@
ctor public MutableFloatList(optional int initialCapacity);
method public boolean add(float element);
method public void add(@IntRange(from=0L) int index, float element);
- method public boolean addAll(androidx.collection.FloatList elements);
- method public boolean addAll(float[] elements);
+ method public inline boolean addAll(androidx.collection.FloatList elements);
+ method public inline boolean addAll(float[] elements);
method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.FloatList elements);
method public boolean addAll(@IntRange(from=0L) int index, float[] elements);
method public void clear();
@@ -1295,9 +1307,9 @@
method public operator void minusAssign(androidx.collection.FloatList elements);
method public inline operator void minusAssign(float element);
method public operator void minusAssign(float[] elements);
- method public operator void plusAssign(androidx.collection.FloatList elements);
+ method public inline operator void plusAssign(androidx.collection.FloatList elements);
method public inline operator void plusAssign(float element);
- method public operator void plusAssign(float[] elements);
+ method public inline operator void plusAssign(float[] elements);
method public boolean remove(float element);
method public boolean removeAll(androidx.collection.FloatList elements);
method public boolean removeAll(float[] elements);
@@ -1415,19 +1427,19 @@
ctor public MutableIntList(optional int initialCapacity);
method public boolean add(int element);
method public void add(@IntRange(from=0L) int index, int element);
- method public boolean addAll(androidx.collection.IntList elements);
+ method public inline boolean addAll(androidx.collection.IntList elements);
method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.IntList elements);
method public boolean addAll(@IntRange(from=0L) int index, int[] elements);
- method public boolean addAll(int[] elements);
+ method public inline boolean addAll(int[] elements);
method public void clear();
method public void ensureCapacity(int capacity);
method public inline int getCapacity();
method public operator void minusAssign(androidx.collection.IntList elements);
method public inline operator void minusAssign(int element);
method public operator void minusAssign(int[] elements);
- method public operator void plusAssign(androidx.collection.IntList elements);
+ method public inline operator void plusAssign(androidx.collection.IntList elements);
method public inline operator void plusAssign(int element);
- method public operator void plusAssign(int[] elements);
+ method public inline operator void plusAssign(int[] elements);
method public boolean remove(int element);
method public boolean removeAll(androidx.collection.IntList elements);
method public boolean removeAll(int[] elements);
@@ -1545,19 +1557,19 @@
ctor public MutableLongList(optional int initialCapacity);
method public void add(@IntRange(from=0L) int index, long element);
method public boolean add(long element);
- method public boolean addAll(androidx.collection.LongList elements);
+ method public inline boolean addAll(androidx.collection.LongList elements);
method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.LongList elements);
method public boolean addAll(@IntRange(from=0L) int index, long[] elements);
- method public boolean addAll(long[] elements);
+ method public inline boolean addAll(long[] elements);
method public void clear();
method public void ensureCapacity(int capacity);
method public inline int getCapacity();
method public operator void minusAssign(androidx.collection.LongList elements);
method public inline operator void minusAssign(long element);
method public operator void minusAssign(long[] elements);
- method public operator void plusAssign(androidx.collection.LongList elements);
+ method public inline operator void plusAssign(androidx.collection.LongList elements);
method public inline operator void plusAssign(long element);
- method public operator void plusAssign(long[] elements);
+ method public inline operator void plusAssign(long[] elements);
method public boolean remove(long element);
method public boolean removeAll(androidx.collection.LongList elements);
method public boolean removeAll(long[] elements);
diff --git a/collection/collection/api/restricted_current.txt b/collection/collection/api/restricted_current.txt
index 0cd8d39..b5fcbad 100644
--- a/collection/collection/api/restricted_current.txt
+++ b/collection/collection/api/restricted_current.txt
@@ -95,11 +95,14 @@
}
public abstract sealed class DoubleList {
- method public final boolean any();
+ method public final inline boolean any();
method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
+ method public final int binarySearch(int element);
+ method public final int binarySearch(int element, optional int fromIndex);
+ method public final int binarySearch(int element, optional int fromIndex, optional int toIndex);
method public final operator boolean contains(double element);
method public final boolean containsAll(androidx.collection.DoubleList elements);
- method public final int count();
+ method public final inline int count();
method public final inline int count(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
method public final double elementAt(@IntRange(from=0L) int index);
method public final inline double elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Double> defaultValue);
@@ -116,12 +119,12 @@
method public final operator double get(@IntRange(from=0L) int index);
method public final inline kotlin.ranges.IntRange getIndices();
method @IntRange(from=-1L) public final inline int getLastIndex();
- method @IntRange(from=0L) public final int getSize();
+ method @IntRange(from=0L) public final inline int getSize();
method public final int indexOf(double element);
method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
- method public final boolean isEmpty();
- method public final boolean isNotEmpty();
+ method public final inline boolean isEmpty();
+ method public final inline boolean isNotEmpty();
method public final String joinToString();
method public final String joinToString(optional CharSequence separator);
method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
@@ -137,11 +140,11 @@
method public final double last();
method public final inline double last(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
method public final int lastIndexOf(double element);
- method public final boolean none();
+ method public final inline boolean none();
method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Boolean> predicate);
property public final inline kotlin.ranges.IntRange indices;
property @IntRange(from=-1L) public final inline int lastIndex;
- property @IntRange(from=0L) public final int size;
+ property @IntRange(from=0L) public final inline int size;
field @kotlin.PublishedApi internal int _size;
field @kotlin.PublishedApi internal double[] content;
}
@@ -286,11 +289,14 @@
}
public abstract sealed class FloatList {
- method public final boolean any();
+ method public final inline boolean any();
method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
+ method public final int binarySearch(int element);
+ method public final int binarySearch(int element, optional int fromIndex);
+ method public final int binarySearch(int element, optional int fromIndex, optional int toIndex);
method public final operator boolean contains(float element);
method public final boolean containsAll(androidx.collection.FloatList elements);
- method public final int count();
+ method public final inline int count();
method public final inline int count(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
method public final float elementAt(@IntRange(from=0L) int index);
method public final inline float elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Float> defaultValue);
@@ -307,12 +313,12 @@
method public final operator float get(@IntRange(from=0L) int index);
method public final inline kotlin.ranges.IntRange getIndices();
method @IntRange(from=-1L) public final inline int getLastIndex();
- method @IntRange(from=0L) public final int getSize();
+ method @IntRange(from=0L) public final inline int getSize();
method public final int indexOf(float element);
method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
- method public final boolean isEmpty();
- method public final boolean isNotEmpty();
+ method public final inline boolean isEmpty();
+ method public final inline boolean isNotEmpty();
method public final String joinToString();
method public final String joinToString(optional CharSequence separator);
method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
@@ -328,11 +334,11 @@
method public final float last();
method public final inline float last(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
method public final int lastIndexOf(float element);
- method public final boolean none();
+ method public final inline boolean none();
method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
property public final inline kotlin.ranges.IntRange indices;
property @IntRange(from=-1L) public final inline int lastIndex;
- property @IntRange(from=0L) public final int size;
+ property @IntRange(from=0L) public final inline int size;
field @kotlin.PublishedApi internal int _size;
field @kotlin.PublishedApi internal float[] content;
}
@@ -638,11 +644,14 @@
}
public abstract sealed class IntList {
- method public final boolean any();
+ method public final inline boolean any();
method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
+ method public final int binarySearch(int element);
+ method public final int binarySearch(int element, optional int fromIndex);
+ method public final int binarySearch(int element, optional int fromIndex, optional int toIndex);
method public final operator boolean contains(int element);
method public final boolean containsAll(androidx.collection.IntList elements);
- method public final int count();
+ method public final inline int count();
method public final inline int count(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
method public final int elementAt(@IntRange(from=0L) int index);
method public final inline int elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> defaultValue);
@@ -659,12 +668,12 @@
method public final operator int get(@IntRange(from=0L) int index);
method public final inline kotlin.ranges.IntRange getIndices();
method @IntRange(from=-1L) public final inline int getLastIndex();
- method @IntRange(from=0L) public final int getSize();
+ method @IntRange(from=0L) public final inline int getSize();
method public final int indexOf(int element);
method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
- method public final boolean isEmpty();
- method public final boolean isNotEmpty();
+ method public final inline boolean isEmpty();
+ method public final inline boolean isNotEmpty();
method public final String joinToString();
method public final String joinToString(optional CharSequence separator);
method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
@@ -680,11 +689,11 @@
method public final int last();
method public final inline int last(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
method public final int lastIndexOf(int element);
- method public final boolean none();
+ method public final inline boolean none();
method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
property public final inline kotlin.ranges.IntRange indices;
property @IntRange(from=-1L) public final inline int lastIndex;
- property @IntRange(from=0L) public final int size;
+ property @IntRange(from=0L) public final inline int size;
field @kotlin.PublishedApi internal int _size;
field @kotlin.PublishedApi internal int[] content;
}
@@ -979,11 +988,14 @@
}
public abstract sealed class LongList {
- method public final boolean any();
+ method public final inline boolean any();
method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
+ method public final int binarySearch(int element);
+ method public final int binarySearch(int element, optional int fromIndex);
+ method public final int binarySearch(int element, optional int fromIndex, optional int toIndex);
method public final operator boolean contains(long element);
method public final boolean containsAll(androidx.collection.LongList elements);
- method public final int count();
+ method public final inline int count();
method public final inline int count(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
method public final long elementAt(@IntRange(from=0L) int index);
method public final inline long elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Long> defaultValue);
@@ -1000,12 +1012,12 @@
method public final operator long get(@IntRange(from=0L) int index);
method public final inline kotlin.ranges.IntRange getIndices();
method @IntRange(from=-1L) public final inline int getLastIndex();
- method @IntRange(from=0L) public final int getSize();
+ method @IntRange(from=0L) public final inline int getSize();
method public final int indexOf(long element);
method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
- method public final boolean isEmpty();
- method public final boolean isNotEmpty();
+ method public final inline boolean isEmpty();
+ method public final inline boolean isNotEmpty();
method public final String joinToString();
method public final String joinToString(optional CharSequence separator);
method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
@@ -1021,11 +1033,11 @@
method public final long last();
method public final inline long last(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
method public final int lastIndexOf(long element);
- method public final boolean none();
+ method public final inline boolean none();
method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
property public final inline kotlin.ranges.IntRange indices;
property @IntRange(from=-1L) public final inline int lastIndex;
- property @IntRange(from=0L) public final int size;
+ property @IntRange(from=0L) public final inline int size;
field @kotlin.PublishedApi internal int _size;
field @kotlin.PublishedApi internal long[] content;
}
@@ -1287,8 +1299,8 @@
ctor public MutableDoubleList(optional int initialCapacity);
method public boolean add(double element);
method public void add(@IntRange(from=0L) int index, double element);
- method public boolean addAll(androidx.collection.DoubleList elements);
- method public boolean addAll(double[] elements);
+ method public inline boolean addAll(androidx.collection.DoubleList elements);
+ method public inline boolean addAll(double[] elements);
method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.DoubleList elements);
method public boolean addAll(@IntRange(from=0L) int index, double[] elements);
method public void clear();
@@ -1297,9 +1309,9 @@
method public operator void minusAssign(androidx.collection.DoubleList elements);
method public inline operator void minusAssign(double element);
method public operator void minusAssign(double[] elements);
- method public operator void plusAssign(androidx.collection.DoubleList elements);
+ method public inline operator void plusAssign(androidx.collection.DoubleList elements);
method public inline operator void plusAssign(double element);
- method public operator void plusAssign(double[] elements);
+ method public inline operator void plusAssign(double[] elements);
method public boolean remove(double element);
method public boolean removeAll(androidx.collection.DoubleList elements);
method public boolean removeAll(double[] elements);
@@ -1361,8 +1373,8 @@
ctor public MutableFloatList(optional int initialCapacity);
method public boolean add(float element);
method public void add(@IntRange(from=0L) int index, float element);
- method public boolean addAll(androidx.collection.FloatList elements);
- method public boolean addAll(float[] elements);
+ method public inline boolean addAll(androidx.collection.FloatList elements);
+ method public inline boolean addAll(float[] elements);
method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.FloatList elements);
method public boolean addAll(@IntRange(from=0L) int index, float[] elements);
method public void clear();
@@ -1371,9 +1383,9 @@
method public operator void minusAssign(androidx.collection.FloatList elements);
method public inline operator void minusAssign(float element);
method public operator void minusAssign(float[] elements);
- method public operator void plusAssign(androidx.collection.FloatList elements);
+ method public inline operator void plusAssign(androidx.collection.FloatList elements);
method public inline operator void plusAssign(float element);
- method public operator void plusAssign(float[] elements);
+ method public inline operator void plusAssign(float[] elements);
method public boolean remove(float element);
method public boolean removeAll(androidx.collection.FloatList elements);
method public boolean removeAll(float[] elements);
@@ -1495,19 +1507,19 @@
ctor public MutableIntList(optional int initialCapacity);
method public boolean add(int element);
method public void add(@IntRange(from=0L) int index, int element);
- method public boolean addAll(androidx.collection.IntList elements);
+ method public inline boolean addAll(androidx.collection.IntList elements);
method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.IntList elements);
method public boolean addAll(@IntRange(from=0L) int index, int[] elements);
- method public boolean addAll(int[] elements);
+ method public inline boolean addAll(int[] elements);
method public void clear();
method public void ensureCapacity(int capacity);
method public inline int getCapacity();
method public operator void minusAssign(androidx.collection.IntList elements);
method public inline operator void minusAssign(int element);
method public operator void minusAssign(int[] elements);
- method public operator void plusAssign(androidx.collection.IntList elements);
+ method public inline operator void plusAssign(androidx.collection.IntList elements);
method public inline operator void plusAssign(int element);
- method public operator void plusAssign(int[] elements);
+ method public inline operator void plusAssign(int[] elements);
method public boolean remove(int element);
method public boolean removeAll(androidx.collection.IntList elements);
method public boolean removeAll(int[] elements);
@@ -1629,19 +1641,19 @@
ctor public MutableLongList(optional int initialCapacity);
method public void add(@IntRange(from=0L) int index, long element);
method public boolean add(long element);
- method public boolean addAll(androidx.collection.LongList elements);
+ method public inline boolean addAll(androidx.collection.LongList elements);
method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.LongList elements);
method public boolean addAll(@IntRange(from=0L) int index, long[] elements);
- method public boolean addAll(long[] elements);
+ method public inline boolean addAll(long[] elements);
method public void clear();
method public void ensureCapacity(int capacity);
method public inline int getCapacity();
method public operator void minusAssign(androidx.collection.LongList elements);
method public inline operator void minusAssign(long element);
method public operator void minusAssign(long[] elements);
- method public operator void plusAssign(androidx.collection.LongList elements);
+ method public inline operator void plusAssign(androidx.collection.LongList elements);
method public inline operator void plusAssign(long element);
- method public operator void plusAssign(long[] elements);
+ method public inline operator void plusAssign(long[] elements);
method public boolean remove(long element);
method public boolean removeAll(androidx.collection.LongList elements);
method public boolean removeAll(long[] elements);
diff --git a/collection/collection/bcv/native/current.txt b/collection/collection/bcv/native/current.txt
index a189609..215b18d 100644
--- a/collection/collection/bcv/native/current.txt
+++ b/collection/collection/bcv/native/current.txt
@@ -468,16 +468,12 @@
final fun add(kotlin/Double): kotlin/Boolean // androidx.collection/MutableDoubleList.add|add(kotlin.Double){}[0]
final fun add(kotlin/Int, kotlin/Double) // androidx.collection/MutableDoubleList.add|add(kotlin.Int;kotlin.Double){}[0]
- final fun addAll(androidx.collection/DoubleList): kotlin/Boolean // androidx.collection/MutableDoubleList.addAll|addAll(androidx.collection.DoubleList){}[0]
- final fun addAll(kotlin/DoubleArray): kotlin/Boolean // androidx.collection/MutableDoubleList.addAll|addAll(kotlin.DoubleArray){}[0]
final fun addAll(kotlin/Int, androidx.collection/DoubleList): kotlin/Boolean // androidx.collection/MutableDoubleList.addAll|addAll(kotlin.Int;androidx.collection.DoubleList){}[0]
final fun addAll(kotlin/Int, kotlin/DoubleArray): kotlin/Boolean // androidx.collection/MutableDoubleList.addAll|addAll(kotlin.Int;kotlin.DoubleArray){}[0]
final fun clear() // androidx.collection/MutableDoubleList.clear|clear(){}[0]
final fun ensureCapacity(kotlin/Int) // androidx.collection/MutableDoubleList.ensureCapacity|ensureCapacity(kotlin.Int){}[0]
final fun minusAssign(androidx.collection/DoubleList) // androidx.collection/MutableDoubleList.minusAssign|minusAssign(androidx.collection.DoubleList){}[0]
final fun minusAssign(kotlin/DoubleArray) // androidx.collection/MutableDoubleList.minusAssign|minusAssign(kotlin.DoubleArray){}[0]
- final fun plusAssign(androidx.collection/DoubleList) // androidx.collection/MutableDoubleList.plusAssign|plusAssign(androidx.collection.DoubleList){}[0]
- final fun plusAssign(kotlin/DoubleArray) // androidx.collection/MutableDoubleList.plusAssign|plusAssign(kotlin.DoubleArray){}[0]
final fun remove(kotlin/Double): kotlin/Boolean // androidx.collection/MutableDoubleList.remove|remove(kotlin.Double){}[0]
final fun removeAll(androidx.collection/DoubleList): kotlin/Boolean // androidx.collection/MutableDoubleList.removeAll|removeAll(androidx.collection.DoubleList){}[0]
final fun removeAll(kotlin/DoubleArray): kotlin/Boolean // androidx.collection/MutableDoubleList.removeAll|removeAll(kotlin.DoubleArray){}[0]
@@ -489,8 +485,12 @@
final fun sort() // androidx.collection/MutableDoubleList.sort|sort(){}[0]
final fun sortDescending() // androidx.collection/MutableDoubleList.sortDescending|sortDescending(){}[0]
final fun trim(kotlin/Int = ...) // androidx.collection/MutableDoubleList.trim|trim(kotlin.Int){}[0]
+ final inline fun addAll(androidx.collection/DoubleList): kotlin/Boolean // androidx.collection/MutableDoubleList.addAll|addAll(androidx.collection.DoubleList){}[0]
+ final inline fun addAll(kotlin/DoubleArray): kotlin/Boolean // androidx.collection/MutableDoubleList.addAll|addAll(kotlin.DoubleArray){}[0]
final inline fun minusAssign(kotlin/Double) // androidx.collection/MutableDoubleList.minusAssign|minusAssign(kotlin.Double){}[0]
+ final inline fun plusAssign(androidx.collection/DoubleList) // androidx.collection/MutableDoubleList.plusAssign|plusAssign(androidx.collection.DoubleList){}[0]
final inline fun plusAssign(kotlin/Double) // androidx.collection/MutableDoubleList.plusAssign|plusAssign(kotlin.Double){}[0]
+ final inline fun plusAssign(kotlin/DoubleArray) // androidx.collection/MutableDoubleList.plusAssign|plusAssign(kotlin.DoubleArray){}[0]
}
final class androidx.collection/MutableFloatFloatMap : androidx.collection/FloatFloatMap { // androidx.collection/MutableFloatFloatMap|null[0]
@@ -543,16 +543,12 @@
final fun add(kotlin/Float): kotlin/Boolean // androidx.collection/MutableFloatList.add|add(kotlin.Float){}[0]
final fun add(kotlin/Int, kotlin/Float) // androidx.collection/MutableFloatList.add|add(kotlin.Int;kotlin.Float){}[0]
- final fun addAll(androidx.collection/FloatList): kotlin/Boolean // androidx.collection/MutableFloatList.addAll|addAll(androidx.collection.FloatList){}[0]
- final fun addAll(kotlin/FloatArray): kotlin/Boolean // androidx.collection/MutableFloatList.addAll|addAll(kotlin.FloatArray){}[0]
final fun addAll(kotlin/Int, androidx.collection/FloatList): kotlin/Boolean // androidx.collection/MutableFloatList.addAll|addAll(kotlin.Int;androidx.collection.FloatList){}[0]
final fun addAll(kotlin/Int, kotlin/FloatArray): kotlin/Boolean // androidx.collection/MutableFloatList.addAll|addAll(kotlin.Int;kotlin.FloatArray){}[0]
final fun clear() // androidx.collection/MutableFloatList.clear|clear(){}[0]
final fun ensureCapacity(kotlin/Int) // androidx.collection/MutableFloatList.ensureCapacity|ensureCapacity(kotlin.Int){}[0]
final fun minusAssign(androidx.collection/FloatList) // androidx.collection/MutableFloatList.minusAssign|minusAssign(androidx.collection.FloatList){}[0]
final fun minusAssign(kotlin/FloatArray) // androidx.collection/MutableFloatList.minusAssign|minusAssign(kotlin.FloatArray){}[0]
- final fun plusAssign(androidx.collection/FloatList) // androidx.collection/MutableFloatList.plusAssign|plusAssign(androidx.collection.FloatList){}[0]
- final fun plusAssign(kotlin/FloatArray) // androidx.collection/MutableFloatList.plusAssign|plusAssign(kotlin.FloatArray){}[0]
final fun remove(kotlin/Float): kotlin/Boolean // androidx.collection/MutableFloatList.remove|remove(kotlin.Float){}[0]
final fun removeAll(androidx.collection/FloatList): kotlin/Boolean // androidx.collection/MutableFloatList.removeAll|removeAll(androidx.collection.FloatList){}[0]
final fun removeAll(kotlin/FloatArray): kotlin/Boolean // androidx.collection/MutableFloatList.removeAll|removeAll(kotlin.FloatArray){}[0]
@@ -564,8 +560,12 @@
final fun sort() // androidx.collection/MutableFloatList.sort|sort(){}[0]
final fun sortDescending() // androidx.collection/MutableFloatList.sortDescending|sortDescending(){}[0]
final fun trim(kotlin/Int = ...) // androidx.collection/MutableFloatList.trim|trim(kotlin.Int){}[0]
+ final inline fun addAll(androidx.collection/FloatList): kotlin/Boolean // androidx.collection/MutableFloatList.addAll|addAll(androidx.collection.FloatList){}[0]
+ final inline fun addAll(kotlin/FloatArray): kotlin/Boolean // androidx.collection/MutableFloatList.addAll|addAll(kotlin.FloatArray){}[0]
final inline fun minusAssign(kotlin/Float) // androidx.collection/MutableFloatList.minusAssign|minusAssign(kotlin.Float){}[0]
+ final inline fun plusAssign(androidx.collection/FloatList) // androidx.collection/MutableFloatList.plusAssign|plusAssign(androidx.collection.FloatList){}[0]
final inline fun plusAssign(kotlin/Float) // androidx.collection/MutableFloatList.plusAssign|plusAssign(kotlin.Float){}[0]
+ final inline fun plusAssign(kotlin/FloatArray) // androidx.collection/MutableFloatList.plusAssign|plusAssign(kotlin.FloatArray){}[0]
}
final class androidx.collection/MutableFloatLongMap : androidx.collection/FloatLongMap { // androidx.collection/MutableFloatLongMap|null[0]
@@ -658,16 +658,12 @@
final fun add(kotlin/Int): kotlin/Boolean // androidx.collection/MutableIntList.add|add(kotlin.Int){}[0]
final fun add(kotlin/Int, kotlin/Int) // androidx.collection/MutableIntList.add|add(kotlin.Int;kotlin.Int){}[0]
- final fun addAll(androidx.collection/IntList): kotlin/Boolean // androidx.collection/MutableIntList.addAll|addAll(androidx.collection.IntList){}[0]
final fun addAll(kotlin/Int, androidx.collection/IntList): kotlin/Boolean // androidx.collection/MutableIntList.addAll|addAll(kotlin.Int;androidx.collection.IntList){}[0]
final fun addAll(kotlin/Int, kotlin/IntArray): kotlin/Boolean // androidx.collection/MutableIntList.addAll|addAll(kotlin.Int;kotlin.IntArray){}[0]
- final fun addAll(kotlin/IntArray): kotlin/Boolean // androidx.collection/MutableIntList.addAll|addAll(kotlin.IntArray){}[0]
final fun clear() // androidx.collection/MutableIntList.clear|clear(){}[0]
final fun ensureCapacity(kotlin/Int) // androidx.collection/MutableIntList.ensureCapacity|ensureCapacity(kotlin.Int){}[0]
final fun minusAssign(androidx.collection/IntList) // androidx.collection/MutableIntList.minusAssign|minusAssign(androidx.collection.IntList){}[0]
final fun minusAssign(kotlin/IntArray) // androidx.collection/MutableIntList.minusAssign|minusAssign(kotlin.IntArray){}[0]
- final fun plusAssign(androidx.collection/IntList) // androidx.collection/MutableIntList.plusAssign|plusAssign(androidx.collection.IntList){}[0]
- final fun plusAssign(kotlin/IntArray) // androidx.collection/MutableIntList.plusAssign|plusAssign(kotlin.IntArray){}[0]
final fun remove(kotlin/Int): kotlin/Boolean // androidx.collection/MutableIntList.remove|remove(kotlin.Int){}[0]
final fun removeAll(androidx.collection/IntList): kotlin/Boolean // androidx.collection/MutableIntList.removeAll|removeAll(androidx.collection.IntList){}[0]
final fun removeAll(kotlin/IntArray): kotlin/Boolean // androidx.collection/MutableIntList.removeAll|removeAll(kotlin.IntArray){}[0]
@@ -679,8 +675,12 @@
final fun sort() // androidx.collection/MutableIntList.sort|sort(){}[0]
final fun sortDescending() // androidx.collection/MutableIntList.sortDescending|sortDescending(){}[0]
final fun trim(kotlin/Int = ...) // androidx.collection/MutableIntList.trim|trim(kotlin.Int){}[0]
+ final inline fun addAll(androidx.collection/IntList): kotlin/Boolean // androidx.collection/MutableIntList.addAll|addAll(androidx.collection.IntList){}[0]
+ final inline fun addAll(kotlin/IntArray): kotlin/Boolean // androidx.collection/MutableIntList.addAll|addAll(kotlin.IntArray){}[0]
final inline fun minusAssign(kotlin/Int) // androidx.collection/MutableIntList.minusAssign|minusAssign(kotlin.Int){}[0]
+ final inline fun plusAssign(androidx.collection/IntList) // androidx.collection/MutableIntList.plusAssign|plusAssign(androidx.collection.IntList){}[0]
final inline fun plusAssign(kotlin/Int) // androidx.collection/MutableIntList.plusAssign|plusAssign(kotlin.Int){}[0]
+ final inline fun plusAssign(kotlin/IntArray) // androidx.collection/MutableIntList.plusAssign|plusAssign(kotlin.IntArray){}[0]
}
final class androidx.collection/MutableIntLongMap : androidx.collection/IntLongMap { // androidx.collection/MutableIntLongMap|null[0]
@@ -773,16 +773,12 @@
final fun add(kotlin/Int, kotlin/Long) // androidx.collection/MutableLongList.add|add(kotlin.Int;kotlin.Long){}[0]
final fun add(kotlin/Long): kotlin/Boolean // androidx.collection/MutableLongList.add|add(kotlin.Long){}[0]
- final fun addAll(androidx.collection/LongList): kotlin/Boolean // androidx.collection/MutableLongList.addAll|addAll(androidx.collection.LongList){}[0]
final fun addAll(kotlin/Int, androidx.collection/LongList): kotlin/Boolean // androidx.collection/MutableLongList.addAll|addAll(kotlin.Int;androidx.collection.LongList){}[0]
final fun addAll(kotlin/Int, kotlin/LongArray): kotlin/Boolean // androidx.collection/MutableLongList.addAll|addAll(kotlin.Int;kotlin.LongArray){}[0]
- final fun addAll(kotlin/LongArray): kotlin/Boolean // androidx.collection/MutableLongList.addAll|addAll(kotlin.LongArray){}[0]
final fun clear() // androidx.collection/MutableLongList.clear|clear(){}[0]
final fun ensureCapacity(kotlin/Int) // androidx.collection/MutableLongList.ensureCapacity|ensureCapacity(kotlin.Int){}[0]
final fun minusAssign(androidx.collection/LongList) // androidx.collection/MutableLongList.minusAssign|minusAssign(androidx.collection.LongList){}[0]
final fun minusAssign(kotlin/LongArray) // androidx.collection/MutableLongList.minusAssign|minusAssign(kotlin.LongArray){}[0]
- final fun plusAssign(androidx.collection/LongList) // androidx.collection/MutableLongList.plusAssign|plusAssign(androidx.collection.LongList){}[0]
- final fun plusAssign(kotlin/LongArray) // androidx.collection/MutableLongList.plusAssign|plusAssign(kotlin.LongArray){}[0]
final fun remove(kotlin/Long): kotlin/Boolean // androidx.collection/MutableLongList.remove|remove(kotlin.Long){}[0]
final fun removeAll(androidx.collection/LongList): kotlin/Boolean // androidx.collection/MutableLongList.removeAll|removeAll(androidx.collection.LongList){}[0]
final fun removeAll(kotlin/LongArray): kotlin/Boolean // androidx.collection/MutableLongList.removeAll|removeAll(kotlin.LongArray){}[0]
@@ -794,8 +790,12 @@
final fun sort() // androidx.collection/MutableLongList.sort|sort(){}[0]
final fun sortDescending() // androidx.collection/MutableLongList.sortDescending|sortDescending(){}[0]
final fun trim(kotlin/Int = ...) // androidx.collection/MutableLongList.trim|trim(kotlin.Int){}[0]
+ final inline fun addAll(androidx.collection/LongList): kotlin/Boolean // androidx.collection/MutableLongList.addAll|addAll(androidx.collection.LongList){}[0]
+ final inline fun addAll(kotlin/LongArray): kotlin/Boolean // androidx.collection/MutableLongList.addAll|addAll(kotlin.LongArray){}[0]
final inline fun minusAssign(kotlin/Long) // androidx.collection/MutableLongList.minusAssign|minusAssign(kotlin.Long){}[0]
+ final inline fun plusAssign(androidx.collection/LongList) // androidx.collection/MutableLongList.plusAssign|plusAssign(androidx.collection.LongList){}[0]
final inline fun plusAssign(kotlin/Long) // androidx.collection/MutableLongList.plusAssign|plusAssign(kotlin.Long){}[0]
+ final inline fun plusAssign(kotlin/LongArray) // androidx.collection/MutableLongList.plusAssign|plusAssign(kotlin.LongArray){}[0]
}
final class androidx.collection/MutableLongLongMap : androidx.collection/LongLongMap { // androidx.collection/MutableLongLongMap|null[0]
@@ -1439,7 +1439,7 @@
final val lastIndex // androidx.collection/DoubleList.lastIndex|{}lastIndex[0]
final inline fun <get-lastIndex>(): kotlin/Int // androidx.collection/DoubleList.lastIndex.<get-lastIndex>|<get-lastIndex>(){}[0]
final val size // androidx.collection/DoubleList.size|{}size[0]
- final fun <get-size>(): kotlin/Int // androidx.collection/DoubleList.size.<get-size>|<get-size>(){}[0]
+ final inline fun <get-size>(): kotlin/Int // androidx.collection/DoubleList.size.<get-size>|<get-size>(){}[0]
final var _size // androidx.collection/DoubleList._size|{}_size[0]
final fun <get-_size>(): kotlin/Int // androidx.collection/DoubleList._size.<get-_size>|<get-_size>(){}[0]
@@ -1448,25 +1448,23 @@
final fun <get-content>(): kotlin/DoubleArray // androidx.collection/DoubleList.content.<get-content>|<get-content>(){}[0]
final fun <set-content>(kotlin/DoubleArray) // androidx.collection/DoubleList.content.<set-content>|<set-content>(kotlin.DoubleArray){}[0]
- final fun any(): kotlin/Boolean // androidx.collection/DoubleList.any|any(){}[0]
+ final fun binarySearch(kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlin/Int // androidx.collection/DoubleList.binarySearch|binarySearch(kotlin.Int;kotlin.Int;kotlin.Int){}[0]
final fun contains(kotlin/Double): kotlin/Boolean // androidx.collection/DoubleList.contains|contains(kotlin.Double){}[0]
final fun containsAll(androidx.collection/DoubleList): kotlin/Boolean // androidx.collection/DoubleList.containsAll|containsAll(androidx.collection.DoubleList){}[0]
- final fun count(): kotlin/Int // androidx.collection/DoubleList.count|count(){}[0]
final fun elementAt(kotlin/Int): kotlin/Double // androidx.collection/DoubleList.elementAt|elementAt(kotlin.Int){}[0]
final fun first(): kotlin/Double // androidx.collection/DoubleList.first|first(){}[0]
final fun get(kotlin/Int): kotlin/Double // androidx.collection/DoubleList.get|get(kotlin.Int){}[0]
final fun indexOf(kotlin/Double): kotlin/Int // androidx.collection/DoubleList.indexOf|indexOf(kotlin.Double){}[0]
- final fun isEmpty(): kotlin/Boolean // androidx.collection/DoubleList.isEmpty|isEmpty(){}[0]
- final fun isNotEmpty(): kotlin/Boolean // androidx.collection/DoubleList.isNotEmpty|isNotEmpty(){}[0]
final fun joinToString(kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/Int = ..., kotlin/CharSequence = ...): kotlin/String // androidx.collection/DoubleList.joinToString|joinToString(kotlin.CharSequence;kotlin.CharSequence;kotlin.CharSequence;kotlin.Int;kotlin.CharSequence){}[0]
final fun last(): kotlin/Double // androidx.collection/DoubleList.last|last(){}[0]
final fun lastIndexOf(kotlin/Double): kotlin/Int // androidx.collection/DoubleList.lastIndexOf|lastIndexOf(kotlin.Double){}[0]
- final fun none(): kotlin/Boolean // androidx.collection/DoubleList.none|none(){}[0]
final inline fun <#A1: kotlin/Any?> fold(#A1, kotlin/Function2<#A1, kotlin/Double, #A1>): #A1 // androidx.collection/DoubleList.fold|fold(0:0;kotlin.Function2<0:0,kotlin.Double,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldIndexed(#A1, kotlin/Function3<kotlin/Int, #A1, kotlin/Double, #A1>): #A1 // androidx.collection/DoubleList.foldIndexed|foldIndexed(0:0;kotlin.Function3<kotlin.Int,0:0,kotlin.Double,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldRight(#A1, kotlin/Function2<kotlin/Double, #A1, #A1>): #A1 // androidx.collection/DoubleList.foldRight|foldRight(0:0;kotlin.Function2<kotlin.Double,0:0,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldRightIndexed(#A1, kotlin/Function3<kotlin/Int, kotlin/Double, #A1, #A1>): #A1 // androidx.collection/DoubleList.foldRightIndexed|foldRightIndexed(0:0;kotlin.Function3<kotlin.Int,kotlin.Double,0:0,0:0>){0§<kotlin.Any?>}[0]
+ final inline fun any(): kotlin/Boolean // androidx.collection/DoubleList.any|any(){}[0]
final inline fun any(kotlin/Function1<kotlin/Double, kotlin/Boolean>): kotlin/Boolean // androidx.collection/DoubleList.any|any(kotlin.Function1<kotlin.Double,kotlin.Boolean>){}[0]
+ final inline fun count(): kotlin/Int // androidx.collection/DoubleList.count|count(){}[0]
final inline fun count(kotlin/Function1<kotlin/Double, kotlin/Boolean>): kotlin/Int // androidx.collection/DoubleList.count|count(kotlin.Function1<kotlin.Double,kotlin.Boolean>){}[0]
final inline fun elementAtOrElse(kotlin/Int, kotlin/Function1<kotlin/Int, kotlin/Double>): kotlin/Double // androidx.collection/DoubleList.elementAtOrElse|elementAtOrElse(kotlin.Int;kotlin.Function1<kotlin.Int,kotlin.Double>){}[0]
final inline fun first(kotlin/Function1<kotlin/Double, kotlin/Boolean>): kotlin/Double // androidx.collection/DoubleList.first|first(kotlin.Function1<kotlin.Double,kotlin.Boolean>){}[0]
@@ -1476,8 +1474,11 @@
final inline fun forEachReversedIndexed(kotlin/Function2<kotlin/Int, kotlin/Double, kotlin/Unit>) // androidx.collection/DoubleList.forEachReversedIndexed|forEachReversedIndexed(kotlin.Function2<kotlin.Int,kotlin.Double,kotlin.Unit>){}[0]
final inline fun indexOfFirst(kotlin/Function1<kotlin/Double, kotlin/Boolean>): kotlin/Int // androidx.collection/DoubleList.indexOfFirst|indexOfFirst(kotlin.Function1<kotlin.Double,kotlin.Boolean>){}[0]
final inline fun indexOfLast(kotlin/Function1<kotlin/Double, kotlin/Boolean>): kotlin/Int // androidx.collection/DoubleList.indexOfLast|indexOfLast(kotlin.Function1<kotlin.Double,kotlin.Boolean>){}[0]
+ final inline fun isEmpty(): kotlin/Boolean // androidx.collection/DoubleList.isEmpty|isEmpty(){}[0]
+ final inline fun isNotEmpty(): kotlin/Boolean // androidx.collection/DoubleList.isNotEmpty|isNotEmpty(){}[0]
final inline fun joinToString(kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/Int = ..., kotlin/CharSequence = ..., crossinline kotlin/Function1<kotlin/Double, kotlin/CharSequence>): kotlin/String // androidx.collection/DoubleList.joinToString|joinToString(kotlin.CharSequence;kotlin.CharSequence;kotlin.CharSequence;kotlin.Int;kotlin.CharSequence;kotlin.Function1<kotlin.Double,kotlin.CharSequence>){}[0]
final inline fun last(kotlin/Function1<kotlin/Double, kotlin/Boolean>): kotlin/Double // androidx.collection/DoubleList.last|last(kotlin.Function1<kotlin.Double,kotlin.Boolean>){}[0]
+ final inline fun none(): kotlin/Boolean // androidx.collection/DoubleList.none|none(){}[0]
final inline fun reversedAny(kotlin/Function1<kotlin/Double, kotlin/Boolean>): kotlin/Boolean // androidx.collection/DoubleList.reversedAny|reversedAny(kotlin.Function1<kotlin.Double,kotlin.Boolean>){}[0]
open fun equals(kotlin/Any?): kotlin/Boolean // androidx.collection/DoubleList.equals|equals(kotlin.Any?){}[0]
open fun hashCode(): kotlin/Int // androidx.collection/DoubleList.hashCode|hashCode(){}[0]
@@ -1580,7 +1581,7 @@
final val lastIndex // androidx.collection/FloatList.lastIndex|{}lastIndex[0]
final inline fun <get-lastIndex>(): kotlin/Int // androidx.collection/FloatList.lastIndex.<get-lastIndex>|<get-lastIndex>(){}[0]
final val size // androidx.collection/FloatList.size|{}size[0]
- final fun <get-size>(): kotlin/Int // androidx.collection/FloatList.size.<get-size>|<get-size>(){}[0]
+ final inline fun <get-size>(): kotlin/Int // androidx.collection/FloatList.size.<get-size>|<get-size>(){}[0]
final var _size // androidx.collection/FloatList._size|{}_size[0]
final fun <get-_size>(): kotlin/Int // androidx.collection/FloatList._size.<get-_size>|<get-_size>(){}[0]
@@ -1589,25 +1590,23 @@
final fun <get-content>(): kotlin/FloatArray // androidx.collection/FloatList.content.<get-content>|<get-content>(){}[0]
final fun <set-content>(kotlin/FloatArray) // androidx.collection/FloatList.content.<set-content>|<set-content>(kotlin.FloatArray){}[0]
- final fun any(): kotlin/Boolean // androidx.collection/FloatList.any|any(){}[0]
+ final fun binarySearch(kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlin/Int // androidx.collection/FloatList.binarySearch|binarySearch(kotlin.Int;kotlin.Int;kotlin.Int){}[0]
final fun contains(kotlin/Float): kotlin/Boolean // androidx.collection/FloatList.contains|contains(kotlin.Float){}[0]
final fun containsAll(androidx.collection/FloatList): kotlin/Boolean // androidx.collection/FloatList.containsAll|containsAll(androidx.collection.FloatList){}[0]
- final fun count(): kotlin/Int // androidx.collection/FloatList.count|count(){}[0]
final fun elementAt(kotlin/Int): kotlin/Float // androidx.collection/FloatList.elementAt|elementAt(kotlin.Int){}[0]
final fun first(): kotlin/Float // androidx.collection/FloatList.first|first(){}[0]
final fun get(kotlin/Int): kotlin/Float // androidx.collection/FloatList.get|get(kotlin.Int){}[0]
final fun indexOf(kotlin/Float): kotlin/Int // androidx.collection/FloatList.indexOf|indexOf(kotlin.Float){}[0]
- final fun isEmpty(): kotlin/Boolean // androidx.collection/FloatList.isEmpty|isEmpty(){}[0]
- final fun isNotEmpty(): kotlin/Boolean // androidx.collection/FloatList.isNotEmpty|isNotEmpty(){}[0]
final fun joinToString(kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/Int = ..., kotlin/CharSequence = ...): kotlin/String // androidx.collection/FloatList.joinToString|joinToString(kotlin.CharSequence;kotlin.CharSequence;kotlin.CharSequence;kotlin.Int;kotlin.CharSequence){}[0]
final fun last(): kotlin/Float // androidx.collection/FloatList.last|last(){}[0]
final fun lastIndexOf(kotlin/Float): kotlin/Int // androidx.collection/FloatList.lastIndexOf|lastIndexOf(kotlin.Float){}[0]
- final fun none(): kotlin/Boolean // androidx.collection/FloatList.none|none(){}[0]
final inline fun <#A1: kotlin/Any?> fold(#A1, kotlin/Function2<#A1, kotlin/Float, #A1>): #A1 // androidx.collection/FloatList.fold|fold(0:0;kotlin.Function2<0:0,kotlin.Float,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldIndexed(#A1, kotlin/Function3<kotlin/Int, #A1, kotlin/Float, #A1>): #A1 // androidx.collection/FloatList.foldIndexed|foldIndexed(0:0;kotlin.Function3<kotlin.Int,0:0,kotlin.Float,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldRight(#A1, kotlin/Function2<kotlin/Float, #A1, #A1>): #A1 // androidx.collection/FloatList.foldRight|foldRight(0:0;kotlin.Function2<kotlin.Float,0:0,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldRightIndexed(#A1, kotlin/Function3<kotlin/Int, kotlin/Float, #A1, #A1>): #A1 // androidx.collection/FloatList.foldRightIndexed|foldRightIndexed(0:0;kotlin.Function3<kotlin.Int,kotlin.Float,0:0,0:0>){0§<kotlin.Any?>}[0]
+ final inline fun any(): kotlin/Boolean // androidx.collection/FloatList.any|any(){}[0]
final inline fun any(kotlin/Function1<kotlin/Float, kotlin/Boolean>): kotlin/Boolean // androidx.collection/FloatList.any|any(kotlin.Function1<kotlin.Float,kotlin.Boolean>){}[0]
+ final inline fun count(): kotlin/Int // androidx.collection/FloatList.count|count(){}[0]
final inline fun count(kotlin/Function1<kotlin/Float, kotlin/Boolean>): kotlin/Int // androidx.collection/FloatList.count|count(kotlin.Function1<kotlin.Float,kotlin.Boolean>){}[0]
final inline fun elementAtOrElse(kotlin/Int, kotlin/Function1<kotlin/Int, kotlin/Float>): kotlin/Float // androidx.collection/FloatList.elementAtOrElse|elementAtOrElse(kotlin.Int;kotlin.Function1<kotlin.Int,kotlin.Float>){}[0]
final inline fun first(kotlin/Function1<kotlin/Float, kotlin/Boolean>): kotlin/Float // androidx.collection/FloatList.first|first(kotlin.Function1<kotlin.Float,kotlin.Boolean>){}[0]
@@ -1617,8 +1616,11 @@
final inline fun forEachReversedIndexed(kotlin/Function2<kotlin/Int, kotlin/Float, kotlin/Unit>) // androidx.collection/FloatList.forEachReversedIndexed|forEachReversedIndexed(kotlin.Function2<kotlin.Int,kotlin.Float,kotlin.Unit>){}[0]
final inline fun indexOfFirst(kotlin/Function1<kotlin/Float, kotlin/Boolean>): kotlin/Int // androidx.collection/FloatList.indexOfFirst|indexOfFirst(kotlin.Function1<kotlin.Float,kotlin.Boolean>){}[0]
final inline fun indexOfLast(kotlin/Function1<kotlin/Float, kotlin/Boolean>): kotlin/Int // androidx.collection/FloatList.indexOfLast|indexOfLast(kotlin.Function1<kotlin.Float,kotlin.Boolean>){}[0]
+ final inline fun isEmpty(): kotlin/Boolean // androidx.collection/FloatList.isEmpty|isEmpty(){}[0]
+ final inline fun isNotEmpty(): kotlin/Boolean // androidx.collection/FloatList.isNotEmpty|isNotEmpty(){}[0]
final inline fun joinToString(kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/Int = ..., kotlin/CharSequence = ..., crossinline kotlin/Function1<kotlin/Float, kotlin/CharSequence>): kotlin/String // androidx.collection/FloatList.joinToString|joinToString(kotlin.CharSequence;kotlin.CharSequence;kotlin.CharSequence;kotlin.Int;kotlin.CharSequence;kotlin.Function1<kotlin.Float,kotlin.CharSequence>){}[0]
final inline fun last(kotlin/Function1<kotlin/Float, kotlin/Boolean>): kotlin/Float // androidx.collection/FloatList.last|last(kotlin.Function1<kotlin.Float,kotlin.Boolean>){}[0]
+ final inline fun none(): kotlin/Boolean // androidx.collection/FloatList.none|none(){}[0]
final inline fun reversedAny(kotlin/Function1<kotlin/Float, kotlin/Boolean>): kotlin/Boolean // androidx.collection/FloatList.reversedAny|reversedAny(kotlin.Function1<kotlin.Float,kotlin.Boolean>){}[0]
open fun equals(kotlin/Any?): kotlin/Boolean // androidx.collection/FloatList.equals|equals(kotlin.Any?){}[0]
open fun hashCode(): kotlin/Int // androidx.collection/FloatList.hashCode|hashCode(){}[0]
@@ -1800,7 +1802,7 @@
final val lastIndex // androidx.collection/IntList.lastIndex|{}lastIndex[0]
final inline fun <get-lastIndex>(): kotlin/Int // androidx.collection/IntList.lastIndex.<get-lastIndex>|<get-lastIndex>(){}[0]
final val size // androidx.collection/IntList.size|{}size[0]
- final fun <get-size>(): kotlin/Int // androidx.collection/IntList.size.<get-size>|<get-size>(){}[0]
+ final inline fun <get-size>(): kotlin/Int // androidx.collection/IntList.size.<get-size>|<get-size>(){}[0]
final var _size // androidx.collection/IntList._size|{}_size[0]
final fun <get-_size>(): kotlin/Int // androidx.collection/IntList._size.<get-_size>|<get-_size>(){}[0]
@@ -1809,25 +1811,23 @@
final fun <get-content>(): kotlin/IntArray // androidx.collection/IntList.content.<get-content>|<get-content>(){}[0]
final fun <set-content>(kotlin/IntArray) // androidx.collection/IntList.content.<set-content>|<set-content>(kotlin.IntArray){}[0]
- final fun any(): kotlin/Boolean // androidx.collection/IntList.any|any(){}[0]
+ final fun binarySearch(kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlin/Int // androidx.collection/IntList.binarySearch|binarySearch(kotlin.Int;kotlin.Int;kotlin.Int){}[0]
final fun contains(kotlin/Int): kotlin/Boolean // androidx.collection/IntList.contains|contains(kotlin.Int){}[0]
final fun containsAll(androidx.collection/IntList): kotlin/Boolean // androidx.collection/IntList.containsAll|containsAll(androidx.collection.IntList){}[0]
- final fun count(): kotlin/Int // androidx.collection/IntList.count|count(){}[0]
final fun elementAt(kotlin/Int): kotlin/Int // androidx.collection/IntList.elementAt|elementAt(kotlin.Int){}[0]
final fun first(): kotlin/Int // androidx.collection/IntList.first|first(){}[0]
final fun get(kotlin/Int): kotlin/Int // androidx.collection/IntList.get|get(kotlin.Int){}[0]
final fun indexOf(kotlin/Int): kotlin/Int // androidx.collection/IntList.indexOf|indexOf(kotlin.Int){}[0]
- final fun isEmpty(): kotlin/Boolean // androidx.collection/IntList.isEmpty|isEmpty(){}[0]
- final fun isNotEmpty(): kotlin/Boolean // androidx.collection/IntList.isNotEmpty|isNotEmpty(){}[0]
final fun joinToString(kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/Int = ..., kotlin/CharSequence = ...): kotlin/String // androidx.collection/IntList.joinToString|joinToString(kotlin.CharSequence;kotlin.CharSequence;kotlin.CharSequence;kotlin.Int;kotlin.CharSequence){}[0]
final fun last(): kotlin/Int // androidx.collection/IntList.last|last(){}[0]
final fun lastIndexOf(kotlin/Int): kotlin/Int // androidx.collection/IntList.lastIndexOf|lastIndexOf(kotlin.Int){}[0]
- final fun none(): kotlin/Boolean // androidx.collection/IntList.none|none(){}[0]
final inline fun <#A1: kotlin/Any?> fold(#A1, kotlin/Function2<#A1, kotlin/Int, #A1>): #A1 // androidx.collection/IntList.fold|fold(0:0;kotlin.Function2<0:0,kotlin.Int,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldIndexed(#A1, kotlin/Function3<kotlin/Int, #A1, kotlin/Int, #A1>): #A1 // androidx.collection/IntList.foldIndexed|foldIndexed(0:0;kotlin.Function3<kotlin.Int,0:0,kotlin.Int,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldRight(#A1, kotlin/Function2<kotlin/Int, #A1, #A1>): #A1 // androidx.collection/IntList.foldRight|foldRight(0:0;kotlin.Function2<kotlin.Int,0:0,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldRightIndexed(#A1, kotlin/Function3<kotlin/Int, kotlin/Int, #A1, #A1>): #A1 // androidx.collection/IntList.foldRightIndexed|foldRightIndexed(0:0;kotlin.Function3<kotlin.Int,kotlin.Int,0:0,0:0>){0§<kotlin.Any?>}[0]
+ final inline fun any(): kotlin/Boolean // androidx.collection/IntList.any|any(){}[0]
final inline fun any(kotlin/Function1<kotlin/Int, kotlin/Boolean>): kotlin/Boolean // androidx.collection/IntList.any|any(kotlin.Function1<kotlin.Int,kotlin.Boolean>){}[0]
+ final inline fun count(): kotlin/Int // androidx.collection/IntList.count|count(){}[0]
final inline fun count(kotlin/Function1<kotlin/Int, kotlin/Boolean>): kotlin/Int // androidx.collection/IntList.count|count(kotlin.Function1<kotlin.Int,kotlin.Boolean>){}[0]
final inline fun elementAtOrElse(kotlin/Int, kotlin/Function1<kotlin/Int, kotlin/Int>): kotlin/Int // androidx.collection/IntList.elementAtOrElse|elementAtOrElse(kotlin.Int;kotlin.Function1<kotlin.Int,kotlin.Int>){}[0]
final inline fun first(kotlin/Function1<kotlin/Int, kotlin/Boolean>): kotlin/Int // androidx.collection/IntList.first|first(kotlin.Function1<kotlin.Int,kotlin.Boolean>){}[0]
@@ -1837,8 +1837,11 @@
final inline fun forEachReversedIndexed(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>) // androidx.collection/IntList.forEachReversedIndexed|forEachReversedIndexed(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
final inline fun indexOfFirst(kotlin/Function1<kotlin/Int, kotlin/Boolean>): kotlin/Int // androidx.collection/IntList.indexOfFirst|indexOfFirst(kotlin.Function1<kotlin.Int,kotlin.Boolean>){}[0]
final inline fun indexOfLast(kotlin/Function1<kotlin/Int, kotlin/Boolean>): kotlin/Int // androidx.collection/IntList.indexOfLast|indexOfLast(kotlin.Function1<kotlin.Int,kotlin.Boolean>){}[0]
+ final inline fun isEmpty(): kotlin/Boolean // androidx.collection/IntList.isEmpty|isEmpty(){}[0]
+ final inline fun isNotEmpty(): kotlin/Boolean // androidx.collection/IntList.isNotEmpty|isNotEmpty(){}[0]
final inline fun joinToString(kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/Int = ..., kotlin/CharSequence = ..., crossinline kotlin/Function1<kotlin/Int, kotlin/CharSequence>): kotlin/String // androidx.collection/IntList.joinToString|joinToString(kotlin.CharSequence;kotlin.CharSequence;kotlin.CharSequence;kotlin.Int;kotlin.CharSequence;kotlin.Function1<kotlin.Int,kotlin.CharSequence>){}[0]
final inline fun last(kotlin/Function1<kotlin/Int, kotlin/Boolean>): kotlin/Int // androidx.collection/IntList.last|last(kotlin.Function1<kotlin.Int,kotlin.Boolean>){}[0]
+ final inline fun none(): kotlin/Boolean // androidx.collection/IntList.none|none(){}[0]
final inline fun reversedAny(kotlin/Function1<kotlin/Int, kotlin/Boolean>): kotlin/Boolean // androidx.collection/IntList.reversedAny|reversedAny(kotlin.Function1<kotlin.Int,kotlin.Boolean>){}[0]
open fun equals(kotlin/Any?): kotlin/Boolean // androidx.collection/IntList.equals|equals(kotlin.Any?){}[0]
open fun hashCode(): kotlin/Int // androidx.collection/IntList.hashCode|hashCode(){}[0]
@@ -2020,7 +2023,7 @@
final val lastIndex // androidx.collection/LongList.lastIndex|{}lastIndex[0]
final inline fun <get-lastIndex>(): kotlin/Int // androidx.collection/LongList.lastIndex.<get-lastIndex>|<get-lastIndex>(){}[0]
final val size // androidx.collection/LongList.size|{}size[0]
- final fun <get-size>(): kotlin/Int // androidx.collection/LongList.size.<get-size>|<get-size>(){}[0]
+ final inline fun <get-size>(): kotlin/Int // androidx.collection/LongList.size.<get-size>|<get-size>(){}[0]
final var _size // androidx.collection/LongList._size|{}_size[0]
final fun <get-_size>(): kotlin/Int // androidx.collection/LongList._size.<get-_size>|<get-_size>(){}[0]
@@ -2029,25 +2032,23 @@
final fun <get-content>(): kotlin/LongArray // androidx.collection/LongList.content.<get-content>|<get-content>(){}[0]
final fun <set-content>(kotlin/LongArray) // androidx.collection/LongList.content.<set-content>|<set-content>(kotlin.LongArray){}[0]
- final fun any(): kotlin/Boolean // androidx.collection/LongList.any|any(){}[0]
+ final fun binarySearch(kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlin/Int // androidx.collection/LongList.binarySearch|binarySearch(kotlin.Int;kotlin.Int;kotlin.Int){}[0]
final fun contains(kotlin/Long): kotlin/Boolean // androidx.collection/LongList.contains|contains(kotlin.Long){}[0]
final fun containsAll(androidx.collection/LongList): kotlin/Boolean // androidx.collection/LongList.containsAll|containsAll(androidx.collection.LongList){}[0]
- final fun count(): kotlin/Int // androidx.collection/LongList.count|count(){}[0]
final fun elementAt(kotlin/Int): kotlin/Long // androidx.collection/LongList.elementAt|elementAt(kotlin.Int){}[0]
final fun first(): kotlin/Long // androidx.collection/LongList.first|first(){}[0]
final fun get(kotlin/Int): kotlin/Long // androidx.collection/LongList.get|get(kotlin.Int){}[0]
final fun indexOf(kotlin/Long): kotlin/Int // androidx.collection/LongList.indexOf|indexOf(kotlin.Long){}[0]
- final fun isEmpty(): kotlin/Boolean // androidx.collection/LongList.isEmpty|isEmpty(){}[0]
- final fun isNotEmpty(): kotlin/Boolean // androidx.collection/LongList.isNotEmpty|isNotEmpty(){}[0]
final fun joinToString(kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/Int = ..., kotlin/CharSequence = ...): kotlin/String // androidx.collection/LongList.joinToString|joinToString(kotlin.CharSequence;kotlin.CharSequence;kotlin.CharSequence;kotlin.Int;kotlin.CharSequence){}[0]
final fun last(): kotlin/Long // androidx.collection/LongList.last|last(){}[0]
final fun lastIndexOf(kotlin/Long): kotlin/Int // androidx.collection/LongList.lastIndexOf|lastIndexOf(kotlin.Long){}[0]
- final fun none(): kotlin/Boolean // androidx.collection/LongList.none|none(){}[0]
final inline fun <#A1: kotlin/Any?> fold(#A1, kotlin/Function2<#A1, kotlin/Long, #A1>): #A1 // androidx.collection/LongList.fold|fold(0:0;kotlin.Function2<0:0,kotlin.Long,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldIndexed(#A1, kotlin/Function3<kotlin/Int, #A1, kotlin/Long, #A1>): #A1 // androidx.collection/LongList.foldIndexed|foldIndexed(0:0;kotlin.Function3<kotlin.Int,0:0,kotlin.Long,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldRight(#A1, kotlin/Function2<kotlin/Long, #A1, #A1>): #A1 // androidx.collection/LongList.foldRight|foldRight(0:0;kotlin.Function2<kotlin.Long,0:0,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A1: kotlin/Any?> foldRightIndexed(#A1, kotlin/Function3<kotlin/Int, kotlin/Long, #A1, #A1>): #A1 // androidx.collection/LongList.foldRightIndexed|foldRightIndexed(0:0;kotlin.Function3<kotlin.Int,kotlin.Long,0:0,0:0>){0§<kotlin.Any?>}[0]
+ final inline fun any(): kotlin/Boolean // androidx.collection/LongList.any|any(){}[0]
final inline fun any(kotlin/Function1<kotlin/Long, kotlin/Boolean>): kotlin/Boolean // androidx.collection/LongList.any|any(kotlin.Function1<kotlin.Long,kotlin.Boolean>){}[0]
+ final inline fun count(): kotlin/Int // androidx.collection/LongList.count|count(){}[0]
final inline fun count(kotlin/Function1<kotlin/Long, kotlin/Boolean>): kotlin/Int // androidx.collection/LongList.count|count(kotlin.Function1<kotlin.Long,kotlin.Boolean>){}[0]
final inline fun elementAtOrElse(kotlin/Int, kotlin/Function1<kotlin/Int, kotlin/Long>): kotlin/Long // androidx.collection/LongList.elementAtOrElse|elementAtOrElse(kotlin.Int;kotlin.Function1<kotlin.Int,kotlin.Long>){}[0]
final inline fun first(kotlin/Function1<kotlin/Long, kotlin/Boolean>): kotlin/Long // androidx.collection/LongList.first|first(kotlin.Function1<kotlin.Long,kotlin.Boolean>){}[0]
@@ -2057,8 +2058,11 @@
final inline fun forEachReversedIndexed(kotlin/Function2<kotlin/Int, kotlin/Long, kotlin/Unit>) // androidx.collection/LongList.forEachReversedIndexed|forEachReversedIndexed(kotlin.Function2<kotlin.Int,kotlin.Long,kotlin.Unit>){}[0]
final inline fun indexOfFirst(kotlin/Function1<kotlin/Long, kotlin/Boolean>): kotlin/Int // androidx.collection/LongList.indexOfFirst|indexOfFirst(kotlin.Function1<kotlin.Long,kotlin.Boolean>){}[0]
final inline fun indexOfLast(kotlin/Function1<kotlin/Long, kotlin/Boolean>): kotlin/Int // androidx.collection/LongList.indexOfLast|indexOfLast(kotlin.Function1<kotlin.Long,kotlin.Boolean>){}[0]
+ final inline fun isEmpty(): kotlin/Boolean // androidx.collection/LongList.isEmpty|isEmpty(){}[0]
+ final inline fun isNotEmpty(): kotlin/Boolean // androidx.collection/LongList.isNotEmpty|isNotEmpty(){}[0]
final inline fun joinToString(kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/CharSequence = ..., kotlin/Int = ..., kotlin/CharSequence = ..., crossinline kotlin/Function1<kotlin/Long, kotlin/CharSequence>): kotlin/String // androidx.collection/LongList.joinToString|joinToString(kotlin.CharSequence;kotlin.CharSequence;kotlin.CharSequence;kotlin.Int;kotlin.CharSequence;kotlin.Function1<kotlin.Long,kotlin.CharSequence>){}[0]
final inline fun last(kotlin/Function1<kotlin/Long, kotlin/Boolean>): kotlin/Long // androidx.collection/LongList.last|last(kotlin.Function1<kotlin.Long,kotlin.Boolean>){}[0]
+ final inline fun none(): kotlin/Boolean // androidx.collection/LongList.none|none(){}[0]
final inline fun reversedAny(kotlin/Function1<kotlin/Long, kotlin/Boolean>): kotlin/Boolean // androidx.collection/LongList.reversedAny|reversedAny(kotlin.Function1<kotlin.Long,kotlin.Boolean>){}[0]
open fun equals(kotlin/Any?): kotlin/Boolean // androidx.collection/LongList.equals|equals(kotlin.Any?){}[0]
open fun hashCode(): kotlin/Int // androidx.collection/LongList.hashCode|hashCode(){}[0]
diff --git a/collection/collection/build.gradle b/collection/collection/build.gradle
index 7833819..ff66423 100644
--- a/collection/collection/build.gradle
+++ b/collection/collection/build.gradle
@@ -53,7 +53,7 @@
commonMain {
dependencies {
api(libs.kotlinStdlib)
- api(project(":annotation:annotation"))
+ api("androidx.annotation:annotation:1.9.0-alpha02")
}
}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt
index 14dab7f..5da46d1 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt
@@ -60,7 +60,7 @@
/** The number of elements in the [DoubleList]. */
@get:IntRange(from = 0)
- public val size: Int
+ public inline val size: Int
get() = _size
/**
@@ -75,12 +75,12 @@
get() = 0 until _size
/** Returns `true` if the collection has no elements in it. */
- public fun none(): Boolean {
+ public inline fun none(): Boolean {
return isEmpty()
}
/** Returns `true` if there's at least one element in the collection. */
- public fun any(): Boolean {
+ public inline fun any(): Boolean {
return isNotEmpty()
}
@@ -131,7 +131,7 @@
}
/** Returns the number of elements in this list. */
- public fun count(): Int = _size
+ public inline fun count(): Int = _size
/**
* Counts the number of elements matching [predicate].
@@ -290,7 +290,7 @@
*/
public operator fun get(@IntRange(from = 0) index: Int): Double {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
return content[index]
}
@@ -301,7 +301,7 @@
*/
public fun elementAt(@IntRange(from = 0) index: Int): Double {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
return content[index]
}
@@ -363,10 +363,10 @@
}
/** Returns `true` if the [DoubleList] has no elements in it or `false` otherwise. */
- public fun isEmpty(): Boolean = _size == 0
+ public inline fun isEmpty(): Boolean = _size == 0
/** Returns `true` if there are elements in the [DoubleList] or `false` if it is empty. */
- public fun isNotEmpty(): Boolean = _size != 0
+ public inline fun isNotEmpty(): Boolean = _size != 0
/**
* Returns the last element in the [DoubleList] or throws a [NoSuchElementException] if it
@@ -409,6 +409,43 @@
}
/**
+ * Searches this list the specified element in the range defined by [fromIndex] and [toIndex].
+ * The list is expected to be sorted into ascending order according to the natural ordering of
+ * its elements, otherwise the result is undefined.
+ *
+ * [fromIndex] must be >= 0 and < [toIndex], and [toIndex] must be <= [size], otherwise an an
+ * [IndexOutOfBoundsException] will be thrown.
+ *
+ * @return the index of the element if it is contained in the list within the specified range.
+ * otherwise, the inverted insertion point `(-insertionPoint - 1)`. The insertion point is
+ * defined as the index at which the element should be inserted, so that the list remains
+ * sorted.
+ */
+ @JvmOverloads
+ public fun binarySearch(element: Int, fromIndex: Int = 0, toIndex: Int = size): Int {
+ if (fromIndex < 0 || fromIndex >= toIndex || toIndex > _size) {
+ throwIndexOutOfBoundsException("")
+ }
+
+ var low = fromIndex
+ var high = toIndex - 1
+
+ while (low <= high) {
+ val mid = low + high ushr 1
+ val midVal = content[mid]
+ if (midVal < element) {
+ low = mid + 1
+ } else if (midVal > element) {
+ high = mid - 1
+ } else {
+ return mid // key found
+ }
+ }
+
+ return -(low + 1) // key not found.
+ }
+
+ /**
* Creates a String from the elements separated by [separator] and using [prefix] before and
* [postfix] after, if supplied.
*
@@ -539,7 +576,7 @@
*/
public fun add(@IntRange(from = 0) index: Int, element: Double) {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
ensureCapacity(_size + 1)
val content = content
@@ -564,7 +601,7 @@
*/
public fun addAll(@IntRange(from = 0) index: Int, elements: DoubleArray): Boolean {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (elements.isEmpty()) return false
ensureCapacity(_size + elements.size)
@@ -591,7 +628,7 @@
*/
public fun addAll(@IntRange(from = 0) index: Int, elements: DoubleList): Boolean {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (elements.isEmpty()) return false
ensureCapacity(_size + elements._size)
@@ -618,7 +655,7 @@
* Adds all [elements] to the end of the [MutableDoubleList] and returns `true` if the
* [MutableDoubleList] was changed or `false` if [elements] was empty.
*/
- public fun addAll(elements: DoubleList): Boolean {
+ public inline fun addAll(elements: DoubleList): Boolean {
return addAll(_size, elements)
}
@@ -626,17 +663,17 @@
* Adds all [elements] to the end of the [MutableDoubleList] and returns `true` if the
* [MutableDoubleList] was changed or `false` if [elements] was empty.
*/
- public fun addAll(elements: DoubleArray): Boolean {
+ public inline fun addAll(elements: DoubleArray): Boolean {
return addAll(_size, elements)
}
/** Adds all [elements] to the end of the [MutableDoubleList]. */
- public operator fun plusAssign(elements: DoubleList) {
+ public inline operator fun plusAssign(elements: DoubleList) {
addAll(_size, elements)
}
/** Adds all [elements] to the end of the [MutableDoubleList]. */
- public operator fun plusAssign(elements: DoubleArray) {
+ public inline operator fun plusAssign(elements: DoubleArray) {
addAll(_size, elements)
}
@@ -740,7 +777,7 @@
*/
public fun removeAt(@IntRange(from = 0) index: Int): Double {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
val content = content
val item = content[index]
@@ -764,10 +801,10 @@
*/
public fun removeRange(@IntRange(from = 0) start: Int, @IntRange(from = 0) end: Int) {
if (start !in 0.._size || end !in 0.._size) {
- throwIndexOutOfBoundsException("Start ($start) and end ($end) must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (end < start) {
- throwIllegalArgumentException("Start ($start) is more than end ($end)")
+ throwIllegalArgumentException("")
}
if (end != start) {
if (end < _size) {
@@ -824,7 +861,7 @@
*/
public operator fun set(@IntRange(from = 0) index: Int, element: Double): Double {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("set index $index must be between 0 .. $lastIndex")
+ throwIndexOutOfBoundsException("")
}
val content = content
val old = content[index]
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
index 55812f3..f8fa448 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
@@ -60,7 +60,7 @@
/** The number of elements in the [FloatList]. */
@get:IntRange(from = 0)
- public val size: Int
+ public inline val size: Int
get() = _size
/** Returns the last valid index in the [FloatList]. This can be `-1` when the list is empty. */
@@ -73,12 +73,12 @@
get() = 0 until _size
/** Returns `true` if the collection has no elements in it. */
- public fun none(): Boolean {
+ public inline fun none(): Boolean {
return isEmpty()
}
/** Returns `true` if there's at least one element in the collection. */
- public fun any(): Boolean {
+ public inline fun any(): Boolean {
return isNotEmpty()
}
@@ -129,7 +129,7 @@
}
/** Returns the number of elements in this list. */
- public fun count(): Int = _size
+ public inline fun count(): Int = _size
/**
* Counts the number of elements matching [predicate].
@@ -288,7 +288,7 @@
*/
public operator fun get(@IntRange(from = 0) index: Int): Float {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
return content[index]
}
@@ -299,7 +299,7 @@
*/
public fun elementAt(@IntRange(from = 0) index: Int): Float {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
return content[index]
}
@@ -361,10 +361,10 @@
}
/** Returns `true` if the [FloatList] has no elements in it or `false` otherwise. */
- public fun isEmpty(): Boolean = _size == 0
+ public inline fun isEmpty(): Boolean = _size == 0
/** Returns `true` if there are elements in the [FloatList] or `false` if it is empty. */
- public fun isNotEmpty(): Boolean = _size != 0
+ public inline fun isNotEmpty(): Boolean = _size != 0
/**
* Returns the last element in the [FloatList] or throws a [NoSuchElementException] if it
@@ -407,6 +407,43 @@
}
/**
+ * Searches this list the specified element in the range defined by [fromIndex] and [toIndex].
+ * The list is expected to be sorted into ascending order according to the natural ordering of
+ * its elements, otherwise the result is undefined.
+ *
+ * [fromIndex] must be >= 0 and < [toIndex], and [toIndex] must be <= [size], otherwise an an
+ * [IndexOutOfBoundsException] will be thrown.
+ *
+ * @return the index of the element if it is contained in the list within the specified range.
+ * otherwise, the inverted insertion point `(-insertionPoint - 1)`. The insertion point is
+ * defined as the index at which the element should be inserted, so that the list remains
+ * sorted.
+ */
+ @JvmOverloads
+ public fun binarySearch(element: Int, fromIndex: Int = 0, toIndex: Int = size): Int {
+ if (fromIndex < 0 || fromIndex >= toIndex || toIndex > _size) {
+ throwIndexOutOfBoundsException("")
+ }
+
+ var low = fromIndex
+ var high = toIndex - 1
+
+ while (low <= high) {
+ val mid = low + high ushr 1
+ val midVal = content[mid]
+ if (midVal < element) {
+ low = mid + 1
+ } else if (midVal > element) {
+ high = mid - 1
+ } else {
+ return mid // key found
+ }
+ }
+
+ return -(low + 1) // key not found.
+ }
+
+ /**
* Creates a String from the elements separated by [separator] and using [prefix] before and
* [postfix] after, if supplied.
*
@@ -536,7 +573,7 @@
*/
public fun add(@IntRange(from = 0) index: Int, element: Float) {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
ensureCapacity(_size + 1)
val content = content
@@ -561,7 +598,7 @@
*/
public fun addAll(@IntRange(from = 0) index: Int, elements: FloatArray): Boolean {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (elements.isEmpty()) return false
ensureCapacity(_size + elements.size)
@@ -588,7 +625,7 @@
*/
public fun addAll(@IntRange(from = 0) index: Int, elements: FloatList): Boolean {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (elements.isEmpty()) return false
ensureCapacity(_size + elements._size)
@@ -615,7 +652,7 @@
* Adds all [elements] to the end of the [MutableFloatList] and returns `true` if the
* [MutableFloatList] was changed or `false` if [elements] was empty.
*/
- public fun addAll(elements: FloatList): Boolean {
+ public inline fun addAll(elements: FloatList): Boolean {
return addAll(_size, elements)
}
@@ -623,17 +660,17 @@
* Adds all [elements] to the end of the [MutableFloatList] and returns `true` if the
* [MutableFloatList] was changed or `false` if [elements] was empty.
*/
- public fun addAll(elements: FloatArray): Boolean {
+ public inline fun addAll(elements: FloatArray): Boolean {
return addAll(_size, elements)
}
/** Adds all [elements] to the end of the [MutableFloatList]. */
- public operator fun plusAssign(elements: FloatList) {
+ public inline operator fun plusAssign(elements: FloatList) {
addAll(_size, elements)
}
/** Adds all [elements] to the end of the [MutableFloatList]. */
- public operator fun plusAssign(elements: FloatArray) {
+ public inline operator fun plusAssign(elements: FloatArray) {
addAll(_size, elements)
}
@@ -737,7 +774,7 @@
*/
public fun removeAt(@IntRange(from = 0) index: Int): Float {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
val content = content
val item = content[index]
@@ -761,10 +798,10 @@
*/
public fun removeRange(@IntRange(from = 0) start: Int, @IntRange(from = 0) end: Int) {
if (start !in 0.._size || end !in 0.._size) {
- throwIndexOutOfBoundsException("Start ($start) and end ($end) must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (end < start) {
- throwIllegalArgumentException("Start ($start) is more than end ($end)")
+ throwIllegalArgumentException("")
}
if (end != start) {
if (end < _size) {
@@ -821,7 +858,7 @@
*/
public operator fun set(@IntRange(from = 0) index: Int, element: Float): Float {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("set index $index must be between 0 .. $lastIndex")
+ throwIndexOutOfBoundsException("")
}
val content = content
val old = content[index]
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
index 6a05353..37f2510 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
@@ -60,7 +60,7 @@
/** The number of elements in the [IntList]. */
@get:IntRange(from = 0)
- public val size: Int
+ public inline val size: Int
get() = _size
/** Returns the last valid index in the [IntList]. This can be `-1` when the list is empty. */
@@ -73,12 +73,12 @@
get() = 0 until _size
/** Returns `true` if the collection has no elements in it. */
- public fun none(): Boolean {
+ public inline fun none(): Boolean {
return isEmpty()
}
/** Returns `true` if there's at least one element in the collection. */
- public fun any(): Boolean {
+ public inline fun any(): Boolean {
return isNotEmpty()
}
@@ -129,7 +129,7 @@
}
/** Returns the number of elements in this list. */
- public fun count(): Int = _size
+ public inline fun count(): Int = _size
/**
* Counts the number of elements matching [predicate].
@@ -288,7 +288,7 @@
*/
public operator fun get(@IntRange(from = 0) index: Int): Int {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
return content[index]
}
@@ -299,7 +299,7 @@
*/
public fun elementAt(@IntRange(from = 0) index: Int): Int {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
return content[index]
}
@@ -359,10 +359,10 @@
}
/** Returns `true` if the [IntList] has no elements in it or `false` otherwise. */
- public fun isEmpty(): Boolean = _size == 0
+ public inline fun isEmpty(): Boolean = _size == 0
/** Returns `true` if there are elements in the [IntList] or `false` if it is empty. */
- public fun isNotEmpty(): Boolean = _size != 0
+ public inline fun isNotEmpty(): Boolean = _size != 0
/**
* Returns the last element in the [IntList] or throws a [NoSuchElementException] if it
@@ -405,6 +405,43 @@
}
/**
+ * Searches this list the specified element in the range defined by [fromIndex] and [toIndex].
+ * The list is expected to be sorted into ascending order according to the natural ordering of
+ * its elements, otherwise the result is undefined.
+ *
+ * [fromIndex] must be >= 0 and < [toIndex], and [toIndex] must be <= [size], otherwise an an
+ * [IndexOutOfBoundsException] will be thrown.
+ *
+ * @return the index of the element if it is contained in the list within the specified range.
+ * otherwise, the inverted insertion point `(-insertionPoint - 1)`. The insertion point is
+ * defined as the index at which the element should be inserted, so that the list remains
+ * sorted.
+ */
+ @JvmOverloads
+ public fun binarySearch(element: Int, fromIndex: Int = 0, toIndex: Int = size): Int {
+ if (fromIndex < 0 || fromIndex >= toIndex || toIndex > _size) {
+ throwIndexOutOfBoundsException("")
+ }
+
+ var low = fromIndex
+ var high = toIndex - 1
+
+ while (low <= high) {
+ val mid = low + high ushr 1
+ val midVal = content[mid]
+ if (midVal < element) {
+ low = mid + 1
+ } else if (midVal > element) {
+ high = mid - 1
+ } else {
+ return mid // key found
+ }
+ }
+
+ return -(low + 1) // key not found.
+ }
+
+ /**
* Creates a String from the elements separated by [separator] and using [prefix] before and
* [postfix] after, if supplied.
*
@@ -533,7 +570,7 @@
*/
public fun add(@IntRange(from = 0) index: Int, element: Int) {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
ensureCapacity(_size + 1)
val content = content
@@ -558,7 +595,7 @@
*/
public fun addAll(@IntRange(from = 0) index: Int, elements: IntArray): Boolean {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (elements.isEmpty()) return false
ensureCapacity(_size + elements.size)
@@ -585,7 +622,7 @@
*/
public fun addAll(@IntRange(from = 0) index: Int, elements: IntList): Boolean {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (elements.isEmpty()) return false
ensureCapacity(_size + elements._size)
@@ -612,7 +649,7 @@
* Adds all [elements] to the end of the [MutableIntList] and returns `true` if the
* [MutableIntList] was changed or `false` if [elements] was empty.
*/
- public fun addAll(elements: IntList): Boolean {
+ public inline fun addAll(elements: IntList): Boolean {
return addAll(_size, elements)
}
@@ -620,17 +657,17 @@
* Adds all [elements] to the end of the [MutableIntList] and returns `true` if the
* [MutableIntList] was changed or `false` if [elements] was empty.
*/
- public fun addAll(elements: IntArray): Boolean {
+ public inline fun addAll(elements: IntArray): Boolean {
return addAll(_size, elements)
}
/** Adds all [elements] to the end of the [MutableIntList]. */
- public operator fun plusAssign(elements: IntList) {
+ public inline operator fun plusAssign(elements: IntList) {
addAll(_size, elements)
}
/** Adds all [elements] to the end of the [MutableIntList]. */
- public operator fun plusAssign(elements: IntArray) {
+ public inline operator fun plusAssign(elements: IntArray) {
addAll(_size, elements)
}
@@ -731,7 +768,7 @@
*/
public fun removeAt(@IntRange(from = 0) index: Int): Int {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
val content = content
val item = content[index]
@@ -755,10 +792,10 @@
*/
public fun removeRange(@IntRange(from = 0) start: Int, @IntRange(from = 0) end: Int) {
if (start !in 0.._size || end !in 0.._size) {
- throwIndexOutOfBoundsException("Start ($start) and end ($end) must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (end < start) {
- throwIllegalArgumentException("Start ($start) is more than end ($end)")
+ throwIllegalArgumentException("")
}
if (end != start) {
if (end < _size) {
@@ -815,7 +852,7 @@
*/
public operator fun set(@IntRange(from = 0) index: Int, element: Int): Int {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("set index $index must be between 0 .. $lastIndex")
+ throwIndexOutOfBoundsException("")
}
val content = content
val old = content[index]
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
index b80e23b..522220b 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
@@ -60,7 +60,7 @@
/** The number of elements in the [LongList]. */
@get:IntRange(from = 0)
- public val size: Int
+ public inline val size: Int
get() = _size
/** Returns the last valid index in the [LongList]. This can be `-1` when the list is empty. */
@@ -73,12 +73,12 @@
get() = 0 until _size
/** Returns `true` if the collection has no elements in it. */
- public fun none(): Boolean {
+ public inline fun none(): Boolean {
return isEmpty()
}
/** Returns `true` if there's at least one element in the collection. */
- public fun any(): Boolean {
+ public inline fun any(): Boolean {
return isNotEmpty()
}
@@ -129,7 +129,7 @@
}
/** Returns the number of elements in this list. */
- public fun count(): Int = _size
+ public inline fun count(): Int = _size
/**
* Counts the number of elements matching [predicate].
@@ -288,7 +288,7 @@
*/
public operator fun get(@IntRange(from = 0) index: Int): Long {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
return content[index]
}
@@ -299,7 +299,7 @@
*/
public fun elementAt(@IntRange(from = 0) index: Int): Long {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
return content[index]
}
@@ -360,10 +360,10 @@
}
/** Returns `true` if the [LongList] has no elements in it or `false` otherwise. */
- public fun isEmpty(): Boolean = _size == 0
+ public inline fun isEmpty(): Boolean = _size == 0
/** Returns `true` if there are elements in the [LongList] or `false` if it is empty. */
- public fun isNotEmpty(): Boolean = _size != 0
+ public inline fun isNotEmpty(): Boolean = _size != 0
/**
* Returns the last element in the [LongList] or throws a [NoSuchElementException] if it
@@ -406,6 +406,43 @@
}
/**
+ * Searches this list the specified element in the range defined by [fromIndex] and [toIndex].
+ * The list is expected to be sorted into ascending order according to the natural ordering of
+ * its elements, otherwise the result is undefined.
+ *
+ * [fromIndex] must be >= 0 and < [toIndex], and [toIndex] must be <= [size], otherwise an an
+ * [IndexOutOfBoundsException] will be thrown.
+ *
+ * @return the index of the element if it is contained in the list within the specified range.
+ * otherwise, the inverted insertion point `(-insertionPoint - 1)`. The insertion point is
+ * defined as the index at which the element should be inserted, so that the list remains
+ * sorted.
+ */
+ @JvmOverloads
+ public fun binarySearch(element: Int, fromIndex: Int = 0, toIndex: Int = size): Int {
+ if (fromIndex < 0 || fromIndex >= toIndex || toIndex > _size) {
+ throwIndexOutOfBoundsException("")
+ }
+
+ var low = fromIndex
+ var high = toIndex - 1
+
+ while (low <= high) {
+ val mid = low + high ushr 1
+ val midVal = content[mid]
+ if (midVal < element) {
+ low = mid + 1
+ } else if (midVal > element) {
+ high = mid - 1
+ } else {
+ return mid // key found
+ }
+ }
+
+ return -(low + 1) // key not found.
+ }
+
+ /**
* Creates a String from the elements separated by [separator] and using [prefix] before and
* [postfix] after, if supplied.
*
@@ -534,7 +571,7 @@
*/
public fun add(@IntRange(from = 0) index: Int, element: Long) {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
ensureCapacity(_size + 1)
val content = content
@@ -559,7 +596,7 @@
*/
public fun addAll(@IntRange(from = 0) index: Int, elements: LongArray): Boolean {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (elements.isEmpty()) return false
ensureCapacity(_size + elements.size)
@@ -586,7 +623,7 @@
*/
public fun addAll(@IntRange(from = 0) index: Int, elements: LongList): Boolean {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (elements.isEmpty()) return false
ensureCapacity(_size + elements._size)
@@ -613,7 +650,7 @@
* Adds all [elements] to the end of the [MutableLongList] and returns `true` if the
* [MutableLongList] was changed or `false` if [elements] was empty.
*/
- public fun addAll(elements: LongList): Boolean {
+ public inline fun addAll(elements: LongList): Boolean {
return addAll(_size, elements)
}
@@ -621,17 +658,17 @@
* Adds all [elements] to the end of the [MutableLongList] and returns `true` if the
* [MutableLongList] was changed or `false` if [elements] was empty.
*/
- public fun addAll(elements: LongArray): Boolean {
+ public inline fun addAll(elements: LongArray): Boolean {
return addAll(_size, elements)
}
/** Adds all [elements] to the end of the [MutableLongList]. */
- public operator fun plusAssign(elements: LongList) {
+ public inline operator fun plusAssign(elements: LongList) {
addAll(_size, elements)
}
/** Adds all [elements] to the end of the [MutableLongList]. */
- public operator fun plusAssign(elements: LongArray) {
+ public inline operator fun plusAssign(elements: LongArray) {
addAll(_size, elements)
}
@@ -733,7 +770,7 @@
*/
public fun removeAt(@IntRange(from = 0) index: Int): Long {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
val content = content
val item = content[index]
@@ -757,10 +794,10 @@
*/
public fun removeRange(@IntRange(from = 0) start: Int, @IntRange(from = 0) end: Int) {
if (start !in 0.._size || end !in 0.._size) {
- throwIndexOutOfBoundsException("Start ($start) and end ($end) must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (end < start) {
- throwIllegalArgumentException("Start ($start) is more than end ($end)")
+ throwIllegalArgumentException("")
}
if (end != start) {
if (end < _size) {
@@ -817,7 +854,7 @@
*/
public operator fun set(@IntRange(from = 0) index: Int, element: Long): Long {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("set index $index must be between 0 .. $lastIndex")
+ throwIndexOutOfBoundsException("")
}
val content = content
val old = content[index]
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt
index 4bc3b68..4c324c7 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt
@@ -713,4 +713,16 @@
assertEquals(-1.0, l[2])
assertEquals(10.0, l[3])
}
+
+ @Test
+ fun binarySearchDoubleList() {
+ val l = mutableDoubleListOf(-2.0, -1.0, 2.0, 10.0, 10.0)
+ assertEquals(0, l.binarySearch(-2))
+ assertEquals(2, l.binarySearch(2))
+ assertEquals(3, l.binarySearch(10))
+
+ assertEquals(-1, l.binarySearch(-20))
+ assertEquals(-4, l.binarySearch(3))
+ assertEquals(-6, l.binarySearch(20))
+ }
}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
index cdc37e9..e1b4649c 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
@@ -713,4 +713,16 @@
assertEquals(-1f, l[2])
assertEquals(10f, l[3])
}
+
+ @Test
+ fun binarySearchFloatList() {
+ val l = mutableFloatListOf(-2f, -1f, 2f, 10f, 10f)
+ assertEquals(0, l.binarySearch(-2))
+ assertEquals(2, l.binarySearch(2))
+ assertEquals(3, l.binarySearch(10))
+
+ assertEquals(-1, l.binarySearch(-20))
+ assertEquals(-4, l.binarySearch(3))
+ assertEquals(-6, l.binarySearch(20))
+ }
}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
index ae3bc8f4..cdc0cf9 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
@@ -713,4 +713,16 @@
assertEquals(-1, l[2])
assertEquals(10, l[3])
}
+
+ @Test
+ fun binarySearchIntList() {
+ val l = mutableIntListOf(-2, -1, 2, 10, 10)
+ assertEquals(0, l.binarySearch(-2))
+ assertEquals(2, l.binarySearch(2))
+ assertEquals(3, l.binarySearch(10))
+
+ assertEquals(-1, l.binarySearch(-20))
+ assertEquals(-4, l.binarySearch(3))
+ assertEquals(-6, l.binarySearch(20))
+ }
}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
index 9fdd870..af0536e 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
@@ -713,4 +713,16 @@
assertEquals(-1L, l[2])
assertEquals(10L, l[3])
}
+
+ @Test
+ fun binarySearchLongList() {
+ val l = mutableLongListOf(-2L, -1L, 2L, 10L, 10L)
+ assertEquals(0, l.binarySearch(-2))
+ assertEquals(2, l.binarySearch(2))
+ assertEquals(3, l.binarySearch(10))
+
+ assertEquals(-1, l.binarySearch(-20))
+ assertEquals(-4, l.binarySearch(3))
+ assertEquals(-6, l.binarySearch(20))
+ }
}
diff --git a/collection/collection/template/PKeyList.kt.template b/collection/collection/template/PKeyList.kt.template
index 3afb59d..929c44c 100644
--- a/collection/collection/template/PKeyList.kt.template
+++ b/collection/collection/template/PKeyList.kt.template
@@ -65,7 +65,7 @@
* The number of elements in the [PKeyList].
*/
@get:IntRange(from = 0)
- public val size: Int
+ public inline val size: Int
get() = _size
/**
@@ -82,14 +82,14 @@
/**
* Returns `true` if the collection has no elements in it.
*/
- public fun none(): Boolean {
+ public inline fun none(): Boolean {
return isEmpty()
}
/**
* Returns `true` if there's at least one element in the collection.
*/
- public fun any(): Boolean {
+ public inline fun any(): Boolean {
return isNotEmpty()
}
@@ -146,7 +146,7 @@
/**
* Returns the number of elements in this list.
*/
- public fun count(): Int = _size
+ public inline fun count(): Int = _size
/**
* Counts the number of elements matching [predicate].
@@ -308,7 +308,7 @@
*/
public operator fun get(@IntRange(from = 0) index: Int): PKey {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
return content[index]
}
@@ -319,7 +319,7 @@
*/
public fun elementAt(@IntRange(from = 0) index: Int): PKey {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
return content[index]
}
@@ -384,12 +384,12 @@
/**
* Returns `true` if the [PKeyList] has no elements in it or `false` otherwise.
*/
- public fun isEmpty(): Boolean = _size == 0
+ public inline fun isEmpty(): Boolean = _size == 0
/**
* Returns `true` if there are elements in the [PKeyList] or `false` if it is empty.
*/
- public fun isNotEmpty(): Boolean = _size != 0
+ public inline fun isNotEmpty(): Boolean = _size != 0
/**
* Returns the last element in the [PKeyList] or throws a [NoSuchElementException] if
@@ -431,6 +431,43 @@
}
/**
+ * Searches this list the specified element in the range defined by [fromIndex] and [toIndex].
+ * The list is expected to be sorted into ascending order according to the natural ordering of
+ * its elements, otherwise the result is undefined.
+ *
+ * [fromIndex] must be >= 0 and < [toIndex], and [toIndex] must be <= [size], otherwise an
+ * an [IndexOutOfBoundsException] will be thrown.
+ *
+ * @return the index of the element if it is contained in the list within the specified range.
+ * otherwise, the inverted insertion point `(-insertionPoint - 1)`. The insertion point is
+ * defined as the index at which the element should be inserted, so that the list remains
+ * sorted.
+ */
+ @JvmOverloads
+ public fun binarySearch(element: Int, fromIndex: Int = 0, toIndex: Int = size): Int {
+ if (fromIndex < 0 || fromIndex >= toIndex || toIndex > _size) {
+ throwIndexOutOfBoundsException("")
+ }
+
+ var low = fromIndex
+ var high = toIndex - 1
+
+ while (low <= high) {
+ val mid = low + high ushr 1
+ val midVal = content[mid]
+ if (midVal < element) {
+ low = mid + 1
+ } else if (midVal > element) {
+ high = mid - 1
+ } else {
+ return mid // key found
+ }
+ }
+
+ return -(low + 1) // key not found.
+ }
+
+ /**
* Creates a String from the elements separated by [separator] and using [prefix] before
* and [postfix] after, if supplied.
*
@@ -570,7 +607,7 @@
*/
public fun add(@IntRange(from = 0) index: Int, element: PKey) {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
ensureCapacity(_size + 1)
val content = content
@@ -597,7 +634,7 @@
elements: PKeyArray
): Boolean {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (elements.isEmpty()) return false
ensureCapacity(_size + elements.size)
@@ -626,7 +663,7 @@
elements: PKeyList
): Boolean {
if (index !in 0.._size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (elements.isEmpty()) return false
ensureCapacity(_size + elements._size)
@@ -653,7 +690,7 @@
* Adds all [elements] to the end of the [MutablePKeyList] and returns `true` if the
* [MutablePKeyList] was changed or `false` if [elements] was empty.
*/
- public fun addAll(elements: PKeyList): Boolean {
+ public inline fun addAll(elements: PKeyList): Boolean {
return addAll(_size, elements)
}
@@ -661,21 +698,21 @@
* Adds all [elements] to the end of the [MutablePKeyList] and returns `true` if the
* [MutablePKeyList] was changed or `false` if [elements] was empty.
*/
- public fun addAll(elements: PKeyArray): Boolean {
+ public inline fun addAll(elements: PKeyArray): Boolean {
return addAll(_size, elements)
}
/**
* Adds all [elements] to the end of the [MutablePKeyList].
*/
- public operator fun plusAssign(elements: PKeyList) {
+ public inline operator fun plusAssign(elements: PKeyList) {
addAll(_size, elements)
}
/**
* Adds all [elements] to the end of the [MutablePKeyList].
*/
- public operator fun plusAssign(elements: PKeyArray) {
+ public inline operator fun plusAssign(elements: PKeyArray) {
addAll(_size, elements)
}
@@ -785,7 +822,7 @@
*/
public fun removeAt(@IntRange(from = 0) index: Int): PKey {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ throwIndexOutOfBoundsException("")
}
val content = content
val item = content[index]
@@ -811,10 +848,10 @@
@IntRange(from = 0) end: Int
) {
if (start !in 0.._size || end !in 0.._size) {
- throwIndexOutOfBoundsException("Start ($start) and end ($end) must be in 0..$_size")
+ throwIndexOutOfBoundsException("")
}
if (end < start) {
- throwIllegalArgumentException("Start ($start) is more than end ($end)")
+ throwIllegalArgumentException("")
}
if (end != start) {
if (end < _size) {
@@ -871,7 +908,7 @@
element: PKey
): PKey {
if (index !in 0 until _size) {
- throwIndexOutOfBoundsException("set index $index must be between 0 .. $lastIndex")
+ throwIndexOutOfBoundsException("")
}
val content = content
val old = content[index]
diff --git a/collection/collection/template/PKeyListTest.kt.template b/collection/collection/template/PKeyListTest.kt.template
index f316360..8f3d8e0 100644
--- a/collection/collection/template/PKeyListTest.kt.template
+++ b/collection/collection/template/PKeyListTest.kt.template
@@ -749,4 +749,17 @@
assertEquals(-1KeySuffix, l[2])
assertEquals(10KeySuffix, l[3])
}
+
+ @Test
+ fun binarySearchPKeyList() {
+ val l = mutablePKeyListOf(-2KeySuffix, -1KeySuffix, 2KeySuffix, 10KeySuffix, 10KeySuffix)
+ assertEquals(0, l.binarySearch(-2))
+ assertEquals(2, l.binarySearch(2))
+ assertEquals(3, l.binarySearch(10))
+
+ assertEquals(-1, l.binarySearch(-20))
+ assertEquals(-4, l.binarySearch(3))
+ assertEquals(-6, l.binarySearch(20))
+
+ }
}
diff --git a/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/AnimationBenchmark.kt b/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/AnimationBenchmark.kt
index ecc01b1..565fe581 100644
--- a/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/AnimationBenchmark.kt
+++ b/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/AnimationBenchmark.kt
@@ -128,7 +128,7 @@
val start = AnimationVector4D(0f, 0f, 0f, 0f)
val end = AnimationVector4D(120f, -50f, 256f, 0f)
val anim =
- VectorizedKeyframesSpec<AnimationVector4D>(
+ VectorizedKeyframesSpec(
keyframes =
mapOf(
0 to (start to LinearEasing),
@@ -150,7 +150,7 @@
val start = AnimationVector4D(0f, 0f, 0f, 0f)
val end = AnimationVector4D(120f, -50f, 256f, 0f)
val anim =
- VectorizedKeyframesSpec<AnimationVector4D>(
+ VectorizedKeyframesSpec(
keyframes =
mapOf(
0 to (start to LinearEasing),
diff --git a/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/KeyframesSpecWithSplineBenchmark.kt b/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/KeyframesSpecWithSplineBenchmark.kt
index 9903e53..fdfec66 100644
--- a/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/KeyframesSpecWithSplineBenchmark.kt
+++ b/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/KeyframesSpecWithSplineBenchmark.kt
@@ -107,18 +107,20 @@
val frame0 = playTimeNanosToEvaluate
val frame1 = frame0 + (1000.0f / 60 * 1_000_000).roundToLong()
benchmarkRule.measureRepeated {
- vectorized.getValueFromNanos(
- playTimeNanos = frame0,
- initialValue = initialVector,
- targetValue = targetVector,
- initialVelocity = initialVector
- )
- vectorized.getValueFromNanos(
- playTimeNanos = frame1,
- initialValue = initialVector,
- targetValue = targetVector,
- initialVelocity = initialVector
- )
+ for (i in 0..10) {
+ vectorized.getValueFromNanos(
+ playTimeNanos = frame0,
+ initialValue = initialVector,
+ targetValue = targetVector,
+ initialVelocity = initialVector
+ )
+ vectorized.getValueFromNanos(
+ playTimeNanos = frame1,
+ initialValue = initialVector,
+ targetValue = targetVector,
+ initialVelocity = initialVector
+ )
+ }
}
}
diff --git a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt
index 32d573f..8efa4d2 100644
--- a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt
+++ b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt
@@ -71,6 +71,7 @@
import leakcanary.DetectLeaksAfterTestSuccess
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
@@ -2474,6 +2475,46 @@
}
@Test
+ fun isRunningFalseAfterChildAnimatedVisibilityTransition() {
+ val seekableTransitionState = SeekableTransitionState(AnimStates.From)
+ lateinit var coroutineScope: CoroutineScope
+ lateinit var transition: Transition<AnimStates>
+ var animatedVisibilityTransition: Transition<*>? = null
+
+ rule.mainClock.autoAdvance = false
+
+ rule.setContent {
+ coroutineScope = rememberCoroutineScope()
+ transition = rememberTransition(seekableTransitionState, label = "Test")
+ transition.AnimatedVisibility(
+ visible = { it == AnimStates.To },
+ ) {
+ animatedVisibilityTransition = this.transition
+ Box(Modifier.size(100.dp))
+ }
+ }
+ rule.runOnIdle {
+ assertFalse(transition.isRunning)
+ assertNull(animatedVisibilityTransition)
+ }
+
+ rule.runOnUiThread {
+ coroutineScope.launch { seekableTransitionState.animateTo(AnimStates.To) }
+ }
+ rule.mainClock.advanceTimeBy(50)
+ rule.runOnIdle {
+ assertTrue(transition.isRunning)
+ assertTrue(animatedVisibilityTransition!!.isRunning)
+ }
+
+ rule.mainClock.advanceTimeBy(5000)
+ rule.runOnIdle {
+ assertFalse(transition.isRunning)
+ assertFalse(animatedVisibilityTransition!!.isRunning)
+ }
+ }
+
+ @Test
fun testCleanupAfterDispose() {
fun isObserving(): Boolean {
var active = false
diff --git a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/IntListExtensionTest.kt b/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/IntListExtensionTest.kt
deleted file mode 100644
index a24dda5..0000000
--- a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/IntListExtensionTest.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2023 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.animation.core
-
-import androidx.collection.intListOf
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertThrows
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class IntListExtensionTest {
- @Test
- fun binarySearch() {
- val l = intListOf(1, 3, 5)
- assertEquals(0, l.binarySearch(1))
- assertEquals(-2, l.binarySearch(2))
- assertEquals(1, l.binarySearch(3))
- assertEquals(-3, l.binarySearch(4))
- assertEquals(2, l.binarySearch(5))
-
- assertEquals(-2, l.binarySearch(2, fromIndex = 1))
- assertEquals(-3, l.binarySearch(2, fromIndex = 2))
- assertEquals(-3, l.binarySearch(5, toIndex = l.size - 1))
-
- // toIndex is exclusive, fails with size + 1
- assertThrows(IndexOutOfBoundsException::class.java) {
- l.binarySearch(element = 3, toIndex = l.size + 1)
- }
- assertThrows(IndexOutOfBoundsException::class.java) {
- l.binarySearch(element = 3, fromIndex = -1)
- }
- assertThrows(IllegalArgumentException::class.java) {
- l.binarySearch(element = 3, fromIndex = 1, toIndex = 0)
- }
- }
-}
diff --git a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/KeyframeAnimationTest.kt b/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/KeyframeAnimationTest.kt
index 9fe726a..ad48e92 100644
--- a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/KeyframeAnimationTest.kt
+++ b/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/KeyframeAnimationTest.kt
@@ -48,7 +48,7 @@
val end = start // the same
val fullTime = 400
val animation =
- keyframes<Float> {
+ keyframes {
durationMillis = fullTime
start at 100
0.5f at 200
@@ -66,7 +66,7 @@
fun possibleToOverrideStartAndEndValues() {
val fullTime = 100
val animation =
- keyframes<Float> {
+ keyframes {
durationMillis = fullTime
1f at 0
0f at fullTime
@@ -81,7 +81,7 @@
fun withEasingOnFullDuration() {
val easing = FastOutSlowInEasing
val animation =
- keyframes<Float> {
+ keyframes {
durationMillis = 100
0f at 0 using easing
1f at durationMillis
@@ -95,7 +95,7 @@
fun easingOnTheSecondPart() {
val easing = FastOutSlowInEasing
val animation =
- keyframes<Float> {
+ keyframes {
durationMillis = 200
1f at 100 using easing
2f at durationMillis
@@ -108,7 +108,7 @@
@Test
fun firstPartIsLinearWithEasingOnTheSecondPart() {
val animation =
- keyframes<Float> {
+ keyframes {
durationMillis = 100
0.5f at 50 using FastOutSlowInEasing
1f at durationMillis
@@ -122,11 +122,11 @@
fun testMultiDimensKeyframesWithEasing() {
val easing = FastOutLinearInEasing
val animation =
- keyframes<AnimationVector2D> {
+ keyframes {
durationMillis = 400
AnimationVector(200f, 300f) at 200 using easing
}
- .vectorize(TwoWayConverter<AnimationVector2D, AnimationVector2D>({ it }, { it }))
+ .vectorize(TwoWayConverter({ it }, { it }))
val start = AnimationVector(0f, 0f)
val end = AnimationVector(200f, 400f)
@@ -158,18 +158,17 @@
1f at durationMillis
}
- val animation = keyframes<Float>(config)
+ val animation = keyframes(config)
- val animationReuseConfig = keyframes<Float>(config)
+ val animationReuseConfig = keyframes(config)
- val animationRedeclareConfig =
- keyframes<Float> {
- durationMillis = 500
- 0f at 100
- 0.5f at 200 using FastOutLinearInEasing
- 0.8f at 300
- 1f at durationMillis
- }
+ val animationRedeclareConfig = keyframes {
+ durationMillis = 500
+ 0f at 100
+ 0.5f at 200 using FastOutLinearInEasing
+ 0.8f at 300
+ 1f at durationMillis
+ }
assertTrue(animation != animationReuseConfig)
assertTrue(animation != animationRedeclareConfig)
@@ -178,41 +177,37 @@
@Test
fun testNotEquals1() {
- val animation =
- keyframes<Float> {
- durationMillis = 500
- 0f at 100
- 0.5f at 200 using FastOutLinearInEasing
- 0.8f at 300
- 1f at durationMillis
- }
+ val animation = keyframes {
+ durationMillis = 500
+ 0f at 100
+ 0.5f at 200 using FastOutLinearInEasing
+ 0.8f at 300
+ 1f at durationMillis
+ }
- val animationAlteredDuration =
- keyframes<Float> {
- durationMillis = 700
- 0f at 100
- 0.5f at 200 using FastOutLinearInEasing
- 0.8f at 300
- 1f at durationMillis
- }
+ val animationAlteredDuration = keyframes {
+ durationMillis = 700
+ 0f at 100
+ 0.5f at 200 using FastOutLinearInEasing
+ 0.8f at 300
+ 1f at durationMillis
+ }
- val animationAlteredEasing =
- keyframes<Float> {
- durationMillis = 500
- 0f at 100 using FastOutSlowInEasing
- 0.5f at 200
- 0.8f at 300
- 1f at durationMillis
- }
+ val animationAlteredEasing = keyframes {
+ durationMillis = 500
+ 0f at 100 using FastOutSlowInEasing
+ 0.5f at 200
+ 0.8f at 300
+ 1f at durationMillis
+ }
- val animationAlteredKeyframes =
- keyframes<Float> {
- durationMillis = 500
- 0f at 100
- 0.3f at 200 using FastOutLinearInEasing
- 0.8f at 400
- 1f at durationMillis
- }
+ val animationAlteredKeyframes = keyframes {
+ durationMillis = 500
+ 0f at 100
+ 0.3f at 200 using FastOutLinearInEasing
+ 0.8f at 400
+ 1f at durationMillis
+ }
assertTrue(animation != animationAlteredDuration)
assertTrue(animation != animationAlteredEasing)
@@ -225,7 +220,7 @@
val end = start // the same
val fullTime = 400
val animation =
- keyframes<Float> {
+ keyframes {
durationMillis = fullTime
start atFraction 0.25f
0.5f atFraction 0.5f
@@ -242,7 +237,7 @@
@Test
fun percentageBasedKeyframesWithEasing() {
val animation =
- keyframes<Float> {
+ keyframes {
durationMillis = 100
0.5f atFraction 0.5f using FastOutSlowInEasing
1f atFraction 1f
@@ -260,13 +255,13 @@
// Out of range values should be effectively ignored.
// It should interpolate within the expected time range without issues
val animation =
- keyframes<Float> {
+ keyframes {
durationMillis = duration
delayMillis = delay
- -1f at -delay using LinearEasing
- -2f at -duration using LinearEasing
- -3f at (duration + 50) using LinearEasing
+ (-1f) at -delay using LinearEasing
+ (-2f) at -duration using LinearEasing
+ (-3f) at (duration + 50) using LinearEasing
}
.vectorize(Float.VectorConverter)
@@ -294,13 +289,13 @@
// Out of range values should be effectively ignored.
// It should interpolate within the expected time range without issues
val animation =
- keyframes<Float> {
+ keyframes {
durationMillis = duration
delayMillis = delay
- -1f at -delay using LinearEasing
- -2f at -duration using LinearEasing
- -3f at (duration + 50) using LinearEasing
+ (-1f) at -delay using LinearEasing
+ (-2f) at -duration using LinearEasing
+ (-3f) at (duration + 50) using LinearEasing
// Force initial and target
4f at 0 using LinearEasing
diff --git a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/MonoSplineTest.kt b/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/MonoSplineTest.kt
index 65d9e6f..fa3f502 100644
--- a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/MonoSplineTest.kt
+++ b/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/MonoSplineTest.kt
@@ -22,7 +22,6 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-@OptIn(ExperimentalAnimationSpecApi::class)
@RunWith(JUnit4::class)
class MonoSplineTest {
@Test
diff --git a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/MotionTest.kt b/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/MotionTest.kt
deleted file mode 100644
index ddc271f..0000000
--- a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/MotionTest.kt
+++ /dev/null
@@ -1,47 +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.animation.core
-
-import org.junit.Assert
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class MotionTest {
- @Test
- fun testMotionCopy() {
- val motion = Motion(100f, 200f)
- Assert.assertEquals(motion, motion.copy())
- }
-
- @Test
- fun testMotionCopyOverwriteValue() {
- val motion = Motion(100f, 200f)
- val copy = motion.copy(value = 50f)
- Assert.assertEquals(50f, copy.value)
- Assert.assertEquals(200f, copy.velocity)
- }
-
- @Test
- fun testMotionCopyOverwriteY() {
- val radius = Motion(100f, 200f)
- val copy = radius.copy(velocity = 300f)
- Assert.assertEquals(100f, copy.value)
- Assert.assertEquals(300f, copy.velocity)
- }
-}
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
index 173f3af..0c1f85e 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
@@ -674,7 +674,7 @@
internal constructor(
value: T,
easing: Easing = LinearEasing,
- internal var arcMode: ArcMode = ArcMode.Companion.ArcLinear
+ internal var arcMode: ArcMode = ArcMode.ArcLinear
) : KeyframeBaseEntity<T>(value = value, easing = easing) {
override fun equals(other: Any?): Boolean {
@@ -758,16 +758,17 @@
converter: TwoWayConverter<T, V>
): VectorizedDurationBasedAnimationSpec<V> {
// Allocate so that we don't resize the list even if the initial/last timestamps are missing
- val timestamps = MutableIntList(config.keyframes.size + 2)
- val timeToVectorMap = MutableIntObjectMap<Pair<V, Easing>>(config.keyframes.size)
- config.keyframes.forEach { key, value ->
+ val keyframes = config.keyframes
+ val timestamps = MutableIntList(keyframes.size + 2)
+ val timeToVectorMap = MutableIntObjectMap<Pair<V, Easing>>(keyframes.size)
+ keyframes.forEach { key, value ->
timestamps.add(key)
timeToVectorMap[key] = Pair(converter.convertToVector(value.value), value.easing)
}
- if (!config.keyframes.contains(0)) {
+ if (!keyframes.contains(0)) {
timestamps.add(0, 0)
}
- if (!config.keyframes.contains(config.durationMillis)) {
+ if (!keyframes.contains(config.durationMillis)) {
timestamps.add(config.durationMillis)
}
timestamps.sort()
@@ -832,8 +833,8 @@
* @see KeyframesSpec.KeyframesSpecConfig
*/
@Stable
-public fun <T> keyframes(init: KeyframesSpec.KeyframesSpecConfig<T>.() -> Unit): KeyframesSpec<T> {
- return KeyframesSpec(KeyframesSpec.KeyframesSpecConfig<T>().apply(init))
+public fun <T> keyframes(init: KeyframesSpecConfig<T>.() -> Unit): KeyframesSpec<T> {
+ return KeyframesSpec(KeyframesSpecConfig<T>().apply(init))
}
/**
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt
index 740faa1..45dfee1 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt
@@ -14,12 +14,18 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE")
+
package androidx.compose.animation.core
+import androidx.compose.ui.util.fastCoerceIn
+import kotlin.jvm.JvmField
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.hypot
+import kotlin.math.max
+import kotlin.math.min
import kotlin.math.sin
/**
@@ -41,33 +47,39 @@
arcs =
Array(timePoints.size - 1) { i ->
when (arcModes[i]) {
- ArcStartVertical -> {
+ ArcSplineArcStartVertical -> {
mode = StartVertical
last = mode
}
- ArcStartHorizontal -> {
+ ArcSplineArcStartHorizontal -> {
mode = StartHorizontal
last = mode
}
- ArcStartFlip -> {
+ ArcSplineArcStartFlip -> {
mode = if (last == StartVertical) StartHorizontal else StartVertical
last = mode
}
- ArcStartLinear -> mode = StartLinear
- ArcAbove -> mode = UpArc
- ArcBelow -> mode = DownArc
+ ArcSplineArcStartLinear -> mode = StartLinear
+ ArcSplineArcAbove -> mode = UpArc
+ ArcSplineArcBelow -> mode = DownArc
}
- val dim = y[i].size / 2 + y[i].size % 2
+
+ val yArray = y[i]
+ val yArray1 = y[i + 1]
+ val timeArray = timePoints[i]
+ val timeArray1 = timePoints[i + 1]
+
+ val dim = yArray.size / 2 + yArray.size % 2
Array(dim) { j ->
val k = j * 2
Arc(
mode = mode,
- time1 = timePoints[i],
- time2 = timePoints[i + 1],
- x1 = y[i][k],
- y1 = y[i][k + 1],
- x2 = y[i + 1][k],
- y2 = y[i + 1][k + 1]
+ time1 = timeArray,
+ time2 = timeArray1,
+ x1 = yArray[k],
+ y1 = yArray[k + 1],
+ x2 = yArray1[k],
+ y2 = yArray1[k + 1]
)
}
}
@@ -76,29 +88,36 @@
/** get the values of the at t point in time. */
fun getPos(time: Float, v: FloatArray) {
var t = time
+ val arcs = arcs
+ val lastIndex = arcs.size - 1
+ val start = arcs[0][0].time1
+ val end = arcs[lastIndex][0].time2
+ val size = v.size
+
if (isExtrapolate) {
- if (t < arcs[0][0].time1 || t > arcs[arcs.size - 1][0].time2) {
+ if (t < start || t > end) {
val p: Int
val t0: Float
- if (t > arcs[arcs.size - 1][0].time2) {
- p = arcs.size - 1
- t0 = arcs[arcs.size - 1][0].time2
+ if (t > end) {
+ p = lastIndex
+ t0 = end
} else {
p = 0
- t0 = arcs[0][0].time1
+ t0 = start
}
val dt = t - t0
var i = 0
var j = 0
- while (i < v.size) {
- if (arcs[p][j].isLinear) {
- v[i] = arcs[p][j].getLinearX(t0) + dt * arcs[p][j].getLinearDX()
- v[i + 1] = arcs[p][j].getLinearY(t0) + dt * arcs[p][j].getLinearDY()
+ while (i < size - 1) {
+ val arc = arcs[p][j]
+ if (arc.isLinear) {
+ v[i] = arc.getLinearX(t0) + dt * arc.linearDX
+ v[i + 1] = arc.getLinearY(t0) + dt * arc.linearDY
} else {
- arcs[p][j].setPoint(t0)
- v[i] = arcs[p][j].calcX() + dt * arcs[p][j].calcDX()
- v[i + 1] = arcs[p][j].calcY() + dt * arcs[p][j].calcDY()
+ arc.setPoint(t0)
+ v[i] = arc.calcX() + dt * arc.calcDX()
+ v[i + 1] = arc.calcY() + dt * arc.calcDY()
}
i += 2
j++
@@ -106,12 +125,8 @@
return
}
} else {
- if (t < arcs[0][0].time1) {
- t = arcs[0][0].time1
- }
- if (t > arcs[arcs.size - 1][0].time2) {
- t = arcs[arcs.size - 1][0].time2
- }
+ t = max(t, start)
+ t = min(t, end)
}
// TODO: Consider passing the index from the caller to improve performance
@@ -119,18 +134,18 @@
for (i in arcs.indices) {
var k = 0
var j = 0
- while (j < v.size) {
- if (t <= arcs[i][k].time2) {
- if (arcs[i][k].isLinear) {
- v[j] = arcs[i][k].getLinearX(t)
- v[j + 1] = arcs[i][k].getLinearY(t)
- populated = true
+ while (j < size - 1) {
+ val arc = arcs[i][k]
+ if (t <= arc.time2) {
+ if (arc.isLinear) {
+ v[j] = arc.getLinearX(t)
+ v[j + 1] = arc.getLinearY(t)
} else {
- arcs[i][k].setPoint(t)
- v[j] = arcs[i][k].calcX()
- v[j + 1] = arcs[i][k].calcY()
- populated = true
+ arc.setPoint(t)
+ v[j] = arc.calcX()
+ v[j + 1] = arc.calcY()
}
+ populated = true
}
j += 2
k++
@@ -143,29 +158,27 @@
/** Get the differential which of the curves at point t */
fun getSlope(time: Float, v: FloatArray) {
- var t = time
- if (t < arcs[0][0].time1) {
- t = arcs[0][0].time1
- } else if (t > arcs[arcs.size - 1][0].time2) {
- t = arcs[arcs.size - 1][0].time2
- }
+ val arcs = arcs
+ val t = time.fastCoerceIn(arcs[0][0].time1, arcs[arcs.size - 1][0].time2)
+ val size = v.size
+
var populated = false
// TODO: Consider passing the index from the caller to improve performance
for (i in arcs.indices) {
var j = 0
var k = 0
- while (j < v.size) {
- if (t <= arcs[i][k].time2) {
- if (arcs[i][k].isLinear) {
- v[j] = arcs[i][k].getLinearDX()
- v[j + 1] = arcs[i][k].getLinearDY()
- populated = true
+ while (j < size - 1) {
+ val arc = arcs[i][k]
+ if (t <= arc.time2) {
+ if (arc.isLinear) {
+ v[j] = arc.linearDX
+ v[j + 1] = arc.linearDY
} else {
- arcs[i][k].setPoint(t)
- v[j] = arcs[i][k].calcDX()
- v[j + 1] = arcs[i][k].calcDY()
- populated = true
+ arc.setPoint(t)
+ v[j] = arc.calcDX()
+ v[j + 1] = arc.calcDY()
}
+ populated = true
}
j += 2
k++
@@ -192,46 +205,51 @@
private val lut: FloatArray
private val oneOverDeltaTime: Float
- private val ellipseA: Float
- private val ellipseB: Float
- private val ellipseCenterX: Float // also used to cache the slope in the unused center
- private val ellipseCenterY: Float // also used to cache the slope in the unused center
private val arcVelocity: Float
- private val isVertical: Boolean
+ private val vertical: Float
- val isLinear: Boolean
+ @JvmField internal val ellipseA: Float
+ @JvmField internal val ellipseB: Float
+
+ @JvmField internal val isLinear: Boolean
+
+ // also used to cache the slope in the unused center
+ @JvmField internal val ellipseCenterX: Float
+ // also used to cache the slope in the unused center
+ @JvmField internal val ellipseCenterY: Float
+
+ internal inline val linearDX: Float
+ get() = ellipseCenterX
+
+ internal inline val linearDY: Float
+ get() = ellipseCenterY
init {
val dx = x2 - x1
val dy = y2 - y1
- isVertical =
+ val isVertical =
when (mode) {
StartVertical -> true
UpArc -> dy < 0
DownArc -> dy > 0
else -> false
}
+ vertical = if (isVertical) -1.0f else 1.0f
oneOverDeltaTime = 1 / (this.time2 - this.time1)
+ lut = FloatArray(LutSize)
- var isLinear = false
- if (StartLinear == mode) {
- isLinear = true
- }
+ var isLinear = mode == StartLinear
if (isLinear || abs(dx) < Epsilon || abs(dy) < Epsilon) {
isLinear = true
arcDistance = hypot(dy, dx)
arcVelocity = arcDistance * oneOverDeltaTime
- ellipseCenterX =
- dx / (this.time2 - this.time1) // cache the slope in the unused center
- ellipseCenterY =
- dy / (this.time2 - this.time1) // cache the slope in the unused center
- lut = FloatArray(101)
+ ellipseCenterX = dx * oneOverDeltaTime // cache the slope in the unused center
+ ellipseCenterY = dy * oneOverDeltaTime // cache the slope in the unused center
ellipseA = Float.NaN
ellipseB = Float.NaN
} else {
- lut = FloatArray(101)
- ellipseA = dx * if (isVertical) -1 else 1
- ellipseB = dy * if (isVertical) 1 else -1
+ ellipseA = dx * vertical
+ ellipseB = dy * -vertical
ellipseCenterX = if (isVertical) x2 else x1
ellipseCenterY = if (isVertical) y1 else y2
buildTable(x1, y1, x2, y2)
@@ -241,17 +259,21 @@
}
fun setPoint(time: Float) {
- val percent = (if (isVertical) time2 - time else time - time1) * oneOverDeltaTime
- val angle = PI.toFloat() * 0.5f * lookup(percent)
+ val angle = calcAngle(time)
tmpSinAngle = sin(angle)
tmpCosAngle = cos(angle)
}
- fun calcX(): Float {
+ private inline fun calcAngle(time: Float): Float {
+ val percent = (if (vertical == -1.0f) time2 - time else time - time1) * oneOverDeltaTime
+ return HalfPi * lookup(percent)
+ }
+
+ inline fun calcX(): Float {
return ellipseCenterX + ellipseA * tmpSinAngle
}
- fun calcY(): Float {
+ inline fun calcY(): Float {
return ellipseCenterY + ellipseB * tmpCosAngle
}
@@ -259,14 +281,14 @@
val vx = ellipseA * tmpCosAngle
val vy = -ellipseB * tmpSinAngle
val norm = arcVelocity / hypot(vx, vy)
- return if (isVertical) -vx * norm else vx * norm
+ return vx * vertical * norm
}
fun calcDY(): Float {
val vx = ellipseA * tmpCosAngle
val vy = -ellipseB * tmpSinAngle
val norm = arcVelocity / hypot(vx, vy)
- return if (isVertical) -vy * norm else vy * norm
+ return vy * vertical * norm
}
fun getLinearX(time: Float): Float {
@@ -281,14 +303,6 @@
return y1 + t * (y2 - y1)
}
- fun getLinearDX(): Float {
- return ellipseCenterX
- }
-
- fun getLinearDY(): Float {
- return ellipseCenterY
- }
-
private fun lookup(v: Float): Float {
if (v <= 0) {
return 0.0f
@@ -296,40 +310,48 @@
if (v >= 1) {
return 1.0f
}
- val pos = v * (lut.size - 1)
+ val pos = v * (LutSize - 1)
val iv = pos.toInt()
val off = pos - pos.toInt()
return lut[iv] + off * (lut[iv + 1] - lut[iv])
}
- private fun buildTable(x1: Float, y1: Float, x2: Float, y2: Float) {
+ // Internal to prevent inlining from R8
+ @Suppress("MemberVisibilityCanBePrivate")
+ internal fun buildTable(x1: Float, y1: Float, x2: Float, y2: Float) {
val a = x2 - x1
val b = y1 - y2
var lx = 0f
- var ly = 0f
+ var ly = b // == b * cos(0), because we skip index 0 in the loops below
var dist = 0f
- for (i in ourPercent.indices) {
- val angle = toRadians(90.0 * i / (ourPercent.size - 1)).toFloat()
+
+ val ourPercent = OurPercentCache
+ val lastIndex = ourPercent.size - 1
+ val lut = lut
+
+ for (i in 1..lastIndex) {
+ val angle = toRadians(90.0 * i / lastIndex).toFloat()
val s = sin(angle)
val c = cos(angle)
val px = a * s
val py = b * c
- if (i > 0) {
- dist += hypot((px - lx), (py - ly))
- ourPercent[i] = dist
- }
+ dist += hypot((px - lx), (py - ly)) // we don't want to compute and store dist
+ ourPercent[i] = dist // for i == 0
lx = px
ly = py
}
+
arcDistance = dist
- for (i in ourPercent.indices) {
+ for (i in 1..lastIndex) {
ourPercent[i] /= dist
}
+
+ val lutLastIndex = (LutSize - 1).toFloat()
for (i in lut.indices) {
- val pos = i / (lut.size - 1).toFloat()
+ val pos = i / lutLastIndex
val index = binarySearch(ourPercent, pos)
if (index >= 0) {
- lut[i] = index / (ourPercent.size - 1).toFloat()
+ lut[i] = index / lutLastIndex
} else if (index == -1) {
lut[i] = 0f
} else {
@@ -337,42 +359,33 @@
val p2 = -index - 1
val ans =
(p1 + (pos - ourPercent[p1]) / (ourPercent[p2] - ourPercent[p1])) /
- (ourPercent.size - 1)
+ lastIndex
lut[i] = ans
}
}
}
-
- companion object {
- private var _ourPercent: FloatArray? = null
- private val ourPercent: FloatArray
- get() {
- if (_ourPercent != null) {
- return _ourPercent!!
- }
- _ourPercent = FloatArray(91)
- return _ourPercent!!
- }
-
- private const val Epsilon = 0.001f
- }
- }
-
- companion object {
- const val ArcStartVertical = 1
- const val ArcStartHorizontal = 2
- const val ArcStartFlip = 3
- const val ArcBelow = 4
- const val ArcAbove = 5
- const val ArcStartLinear = 0
- private const val StartVertical = 1
- private const val StartHorizontal = 2
- private const val StartLinear = 3
- private const val DownArc = 4
- private const val UpArc = 5
}
}
+internal const val ArcSplineArcStartLinear = 0
+internal const val ArcSplineArcStartVertical = 1
+internal const val ArcSplineArcStartHorizontal = 2
+internal const val ArcSplineArcStartFlip = 3
+internal const val ArcSplineArcBelow = 4
+internal const val ArcSplineArcAbove = 5
+
+private const val StartVertical = 1
+private const val StartHorizontal = 2
+private const val StartLinear = 3
+private const val DownArc = 4
+private const val UpArc = 5
+private const val LutSize = 101
+
+private const val Epsilon = 0.001f
+private const val HalfPi = (PI * 0.5).toFloat()
+
+private val OurPercentCache: FloatArray = FloatArray(91)
+
internal expect inline fun toRadians(value: Double): Double
internal expect inline fun binarySearch(array: FloatArray, position: Float): Int
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ComplexDouble.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ComplexDouble.kt
deleted file mode 100644
index 0a2ceb2..0000000
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ComplexDouble.kt
+++ /dev/null
@@ -1,110 +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.
- */
-
-@file:Suppress("NOTHING_TO_INLINE")
-
-package androidx.compose.animation.core
-
-import kotlin.math.abs
-import kotlin.math.sqrt
-
-internal data class ComplexDouble(private var _real: Double, private var _imaginary: Double) {
- val real: Double
- get() {
- return _real
- }
-
- val imaginary: Double
- get() {
- return _imaginary
- }
-
- inline operator fun plus(other: Double): ComplexDouble {
- _real += other
- return this
- }
-
- inline operator fun plus(other: ComplexDouble): ComplexDouble {
- _real += other.real
- _imaginary += other.imaginary
- return this
- }
-
- inline operator fun minus(other: Double): ComplexDouble {
- return this + -other
- }
-
- inline operator fun minus(other: ComplexDouble): ComplexDouble {
- return this + -other
- }
-
- inline operator fun times(other: Double): ComplexDouble {
- _real *= other
- _imaginary *= other
- return this
- }
-
- inline operator fun times(other: ComplexDouble): ComplexDouble {
- _real = real * other.real - imaginary * other.imaginary
- _imaginary = real * other.imaginary + other.real * imaginary
- return this
- }
-
- inline operator fun unaryMinus(): ComplexDouble {
- _real *= -1
- _imaginary *= -1
- return this
- }
-
- inline operator fun div(other: Double): ComplexDouble {
- _real /= other
- _imaginary /= other
- return this
- }
-}
-
-/** Returns the roots of the polynomial [a]x^2+[b]x+[c]=0 which may be complex. */
-internal fun complexQuadraticFormula(
- a: Double,
- b: Double,
- c: Double
-): Pair<ComplexDouble, ComplexDouble> {
- val partialRoot = b * b - 4.0 * a * c
- val divisor = 1.0 / (2.0 * a)
- val firstRoot = (-b + complexSqrt(partialRoot)) * divisor
- val secondRoot = (-b - complexSqrt(partialRoot)) * divisor
- return firstRoot to secondRoot
-}
-
-/** Returns the square root of [num] which may be imaginary. */
-internal fun complexSqrt(num: Double): ComplexDouble =
- if (num < 0.0) {
- ComplexDouble(0.0, sqrt(abs(num)))
- } else {
- ComplexDouble(sqrt(num), 0.0)
- }
-
-internal inline operator fun Double.plus(other: ComplexDouble): ComplexDouble {
- return other + this
-}
-
-internal inline operator fun Double.minus(other: ComplexDouble): ComplexDouble {
- return this + -other
-}
-
-internal inline operator fun Double.times(other: ComplexDouble): ComplexDouble {
- return other * this
-}
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/FloatAnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/FloatAnimationSpec.kt
index e0c04a3..98c6625 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/FloatAnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/FloatAnimationSpec.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
+
package androidx.compose.animation.core
import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
@@ -149,8 +151,7 @@
// TODO: Properly support Nanos in the spring impl
val playTimeMillis = playTimeNanos / MillisToNanos
spring.finalPosition = targetValue
- val value = spring.updateValues(initialValue, initialVelocity, playTimeMillis).value
- return value
+ return spring.updateValues(initialValue, initialVelocity, playTimeMillis).value
}
override fun getVelocityFromNanos(
@@ -162,8 +163,7 @@
// TODO: Properly support Nanos in the spring impl
val playTimeMillis = playTimeNanos / MillisToNanos
spring.finalPosition = targetValue
- val velocity = spring.updateValues(initialValue, initialVelocity, playTimeMillis).velocity
- return velocity
+ return spring.updateValues(initialValue, initialVelocity, playTimeMillis).velocity
}
override fun getEndVelocity(
@@ -193,7 +193,7 @@
* specified, the animation will start right away.
*
* @param duration the amount of time (in milliseconds) the animation will take to finish. Defaults
- * to [DefaultDuration]
+ * to [DefaultDurationMillis]
* @param delay the amount of time the animation will wait before it starts running. Defaults to 0.
* @param easing the easing function that will be used to interoplate between the start and end
* value of the animation. Defaults to [FastOutSlowInEasing].
@@ -215,12 +215,12 @@
): Float {
val clampedPlayTimeNanos = clampPlayTimeNanos(playTimeNanos)
val rawFraction = if (duration == 0) 1f else clampedPlayTimeNanos / durationNanos.toFloat()
- val fraction = easing.transform(rawFraction.fastCoerceIn(0f, 1f))
+ val fraction = easing.transform(rawFraction)
return lerp(initialValue, targetValue, fraction)
}
- private fun clampPlayTimeNanos(playTimeNanos: Long): Long {
- return (playTimeNanos - delayNanos).coerceIn(0, durationNanos)
+ private inline fun clampPlayTimeNanos(playTimeNanos: Long): Long {
+ return (playTimeNanos - delayNanos).fastCoerceIn(0, durationNanos)
}
@Suppress("MethodNameUnits")
@@ -229,7 +229,7 @@
targetValue: Float,
initialVelocity: Float
): Long {
- return (delay + duration) * MillisToNanos
+ return delayNanos + durationNanos
}
// Calculate velocity by difference between the current value and the value 1 ms ago. This is a
@@ -242,9 +242,7 @@
initialVelocity: Float
): Float {
val clampedPlayTimeNanos = clampPlayTimeNanos(playTimeNanos)
- if (clampedPlayTimeNanos < 0L) {
- return 0f
- } else if (clampedPlayTimeNanos == 0L) {
+ if (clampedPlayTimeNanos == 0L) {
return initialVelocity
}
val startNum =
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/IntListExtension.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/IntListExtension.kt
deleted file mode 100644
index 5544fcb..0000000
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/IntListExtension.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2023 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.animation.core
-
-import androidx.collection.IntList
-import kotlin.jvm.JvmOverloads
-
-// TODO(b/311454748): Move to :collection as public API once it's back on alpha. Also, add versions
-// for LongList and FloatList.
-
-/**
- * [IntArray.binarySearch] For original documentation.
- *
- * Searches the list or the range of the list for the provided [element] using the binary search
- * algorithm. The list is expected to be sorted, otherwise the result is undefined.
- *
- * If the list contains multiple elements equal to the specified [element], there is no guarantee
- * which one will be found.
- *
- * @param element the to search for.
- * @param fromIndex the start of the range (inclusive) to search in, 0 by default.
- * @param toIndex the end of the range (exclusive) to search in, size of this list by default.
- * @return the index of the element, if it is contained in the list within the specified range;
- * otherwise, the inverted insertion point `(-insertion point - 1)`. The insertion point is
- * defined as the index at which the element should be inserted, so that the list (or the
- * specified subrange of list) still remains sorted.
- * @throws IndexOutOfBoundsException if [fromIndex] is less than zero or [toIndex] is greater than
- * the size of this list.
- * @throws IllegalArgumentException if [fromIndex] is greater than [toIndex].
- */
-@JvmOverloads
-internal fun IntList.binarySearch(element: Int, fromIndex: Int = 0, toIndex: Int = size): Int {
- requirePrecondition(fromIndex <= toIndex) { "fromIndex($fromIndex) > toIndex($toIndex)" }
- if (fromIndex < 0) {
- throw IndexOutOfBoundsException("Index out of range: $fromIndex")
- }
- if (toIndex > size) {
- throw IndexOutOfBoundsException("Index out of range: $toIndex")
- }
-
- var low = fromIndex
- var high = toIndex - 1
-
- while (low <= high) {
- val mid = low + high ushr 1
- val midVal = this[mid]
- if (midVal < element) {
- low = mid + 1
- } else if (midVal > element) {
- high = mid - 1
- } else {
- return mid // key found
- }
- }
- return -(low + 1) // key not found.
-}
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt
index 508d41d..1ce3959 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt
@@ -16,8 +16,11 @@
package androidx.compose.animation.core
+import androidx.compose.ui.util.fastCoerceIn
import kotlin.math.hypot
+private const val MonoSplineIsExtrapolate = true
+
/**
* This performs a spline interpolation in multiple dimensions time is an array of all positions and
* y is a list of arrays each with the values at each point
@@ -26,7 +29,6 @@
private val timePoints: FloatArray
private val values: Array<FloatArray>
private val tangents: Array<FloatArray>
- private val isExtrapolate = true
private val slopeTemp: FloatArray
init {
@@ -88,22 +90,20 @@
/** get the value of the j'th spline at time t */
fun getPos(t: Float, j: Int): Float {
+ val values = values
+ val tangents = tangents
val n = timePoints.size
- if (isExtrapolate) {
- if (t <= timePoints[0]) {
- return values[0][j] + (t - timePoints[0]) * getSlope(timePoints[0], j)
- }
- if (t >= timePoints[n - 1]) {
- return values[n - 1][j] + (t - timePoints[n - 1]) * getSlope(timePoints[n - 1], j)
+ val index = if (t <= timePoints[0]) 0 else (if (t >= timePoints[n - 1]) n - 1 else -1)
+ if (MonoSplineIsExtrapolate) {
+ if (index != -1) {
+ return values[index][j] + (t - timePoints[index]) * getSlope(timePoints[index], j)
}
} else {
- if (t <= timePoints[0]) {
- return values[0][j]
- }
- if (t >= timePoints[n - 1]) {
- return values[n - 1][j]
+ if (index != -1) {
+ return values[index][j]
}
}
+
for (i in 0 until n - 1) {
if (t == timePoints[i]) {
return values[i][j]
@@ -115,7 +115,7 @@
val y2 = values[i + 1][j]
val t1 = tangents[i][j]
val t2 = tangents[i + 1][j]
- return interpolate(h, x, y1, y2, t1, t2)
+ return hermiteInterpolate(h, x, y1, y2, t1, t2)
}
}
return 0.0f // should never reach here
@@ -129,40 +129,30 @@
fun getPos(time: Float, v: AnimationVector, index: Int = 0) {
val n = timePoints.size
val dim = values[0].size
- if (isExtrapolate) {
- if (time <= timePoints[0]) {
- getSlope(timePoints[0], slopeTemp)
+ val k = if (time <= timePoints[0]) 0 else (if (time >= timePoints[n - 1]) n - 1 else -1)
+ if (MonoSplineIsExtrapolate) {
+ if (k != -1) {
+ getSlope(timePoints[k], slopeTemp)
for (j in 0 until dim) {
- v[j] = values[0][j] + (time - timePoints[0]) * slopeTemp[j]
- }
- return
- }
- if (time >= timePoints[n - 1]) {
- getSlope(timePoints[n - 1], slopeTemp)
- for (j in 0 until dim) {
- v[j] = values[n - 1][j] + (time - timePoints[n - 1]) * slopeTemp[j]
+ v[j] = values[k][j] + (time - timePoints[k]) * slopeTemp[j]
}
return
}
} else {
- if (time <= timePoints[0]) {
+ if (k != -1) {
for (j in 0 until dim) {
- v[j] = values[0][j]
- }
- return
- }
- if (time >= timePoints[n - 1]) {
- for (j in 0 until dim) {
- v[j] = values[n - 1][j]
+ v[j] = values[k][j]
}
return
}
}
+
for (i in index until n - 1) {
if (time == timePoints[i]) {
for (j in 0 until dim) {
v[j] = values[i][j]
}
+ return
}
if (time < timePoints[i + 1]) {
val h = timePoints[i + 1] - timePoints[i]
@@ -172,7 +162,7 @@
val y2 = values[i + 1][j]
val t1 = tangents[i][j]
val t2 = tangents[i + 1][j]
- v[j] = interpolate(h, x, y1, y2, t1, t2)
+ v[j] = hermiteInterpolate(h, x, y1, y2, t1, t2)
}
return
}
@@ -180,15 +170,12 @@
}
/** Get the differential of the value at time fill an array of slopes for each spline */
- fun getSlope(time: Float, v: FloatArray) {
- var t = time
- val n = timePoints.size
+ private fun getSlope(time: Float, v: FloatArray) {
val dim = values[0].size
- if (t <= timePoints[0]) {
- t = timePoints[0]
- } else if (t >= timePoints[n - 1]) {
- t = timePoints[n - 1]
- }
+ val n = timePoints.size
+ val t = time.fastCoerceIn(timePoints[0], timePoints[n - 1])
+
+ if (v.size < dim) return
for (i in 0 until n - 1) {
if (t <= timePoints[i + 1]) {
val h = timePoints[i + 1] - timePoints[i]
@@ -198,12 +185,11 @@
val y2 = values[i + 1][j]
val t1 = tangents[i][j]
val t2 = tangents[i + 1][j]
- v[j] = diff(h, x, y1, y2, t1, t2) / h
+ v[j] = hermiteDifferential(h, x, y1, y2, t1, t2) / h
}
break
}
}
- return
}
/**
@@ -213,78 +199,104 @@
* You may provide [index] to simplify searching for the correct keyframe for the given [time].
*/
fun getSlope(time: Float, v: AnimationVector, index: Int = 0) {
- val t = time
+ val timePoints = timePoints
+ val values = values
+ val tangents = tangents
+
val n = timePoints.size
val dim = values[0].size
// If time is 0, max or out of range we directly return the corresponding slope value
- if (t <= timePoints[0]) {
+ val tangentIndex =
+ if (time <= timePoints[0]) 0 else (if (time >= timePoints[n - 1]) n - 1 else -1)
+ if (tangentIndex != -1) {
+ val tangent = tangents[tangentIndex]
+ // Help ART eliminate bound checks
+ if (tangent.size < dim) return
for (j in 0 until dim) {
- v[j] = tangents[0][j]
- }
- return
- } else if (t >= timePoints[n - 1]) {
- for (j in 0 until dim) {
- v[j] = tangents[n - 1][j]
+ v[j] = tangent[j]
}
return
}
// Otherwise, calculate interpolated velocity
for (i in index until n - 1) {
- if (t <= timePoints[i + 1]) {
+ if (time <= timePoints[i + 1]) {
val h = timePoints[i + 1] - timePoints[i]
- val x = (t - timePoints[i]) / h
+ val x = (time - timePoints[i]) / h
for (j in 0 until dim) {
val y1 = values[i][j]
val y2 = values[i + 1][j]
val t1 = tangents[i][j]
val t2 = tangents[i + 1][j]
- v[j] = diff(h, x, y1, y2, t1, t2) / h
+ v[j] = hermiteDifferential(h, x, y1, y2, t1, t2) / h
}
break
}
}
- return
}
private fun getSlope(time: Float, j: Int): Float {
- var t = time
+ val timePoints = timePoints
+ val values = values
+ val tangents = tangents
val n = timePoints.size
- if (t < timePoints[0]) {
- t = timePoints[0]
- } else if (t >= timePoints[n - 1]) {
- t = timePoints[n - 1]
- }
+ val t = time.fastCoerceIn(timePoints[0], timePoints[n - 1])
for (i in 0 until n - 1) {
if (t <= timePoints[i + 1]) {
- val h = timePoints[i + 1] - timePoints[i]
- val x = (t - timePoints[i]) / h
val y1 = values[i][j]
val y2 = values[i + 1][j]
val t1 = tangents[i][j]
val t2 = tangents[i + 1][j]
- return diff(h, x, y1, y2, t1, t2) / h
+ val h = timePoints[i + 1] - timePoints[i]
+ val x = (t - timePoints[i]) / h
+ return hermiteDifferential(h, x, y1, y2, t1, t2) / h
}
}
return 0.0f // should never reach here
}
+}
- /** Cubic Hermite spline */
- private fun interpolate(h: Float, x: Float, y1: Float, y2: Float, t1: Float, t2: Float): Float {
- val x2 = x * x
- val x3 = x2 * x
- return (-2 * x3 * y2 + 3 * x2 * y2 + 2 * x3 * y1 - 3 * x2 * y1 +
- y1 +
- h * t2 * x3 +
- h * t1 * x3 - h * t2 * x2 - 2 * h * t1 * x2 + h * t1 * x)
- }
+/** Cubic Hermite spline */
+internal fun hermiteInterpolate(
+ h: Float,
+ x: Float,
+ y1: Float,
+ y2: Float,
+ t1: Float,
+ t2: Float
+): Float {
+ val x2 = x * x
+ val x3 = x2 * x
+ // The exact formula is as follows:
+ //
+ // -2 * x3 * y2 + 3 * x2 * y2 + 2 * x3 * y1 - 3 * x2 * y1 +
+ // y1 +
+ // h * t2 * x3 +
+ // h * t1 * x3 - h * t2 * x2 - 2 * h * t1 * x2 + h * t1 * x)
+ //
+ // The code below is equivalent but factored to go from 30 down to 20 instructions
+ // on aarch64 devices
+ return h * t1 * (x - 2 * x2 + x3) + h * t2 * (x3 - x2) + y1 - (3 * x2 - 2 * x3) * (y1 - y2)
+}
- /** Cubic Hermite spline slope differentiated */
- private fun diff(h: Float, x: Float, y1: Float, y2: Float, t1: Float, t2: Float): Float {
- val x2 = x * x
- return (-6 * x2 * y2 + 6 * x * y2 + 6 * x2 * y1 - 6 * x * y1 +
- 3 * h * t2 * x2 +
- 3 * h * t1 * x2 - 2 * h * t2 * x - 4 * h * t1 * x + h * t1)
- }
+/** Cubic Hermite spline slope differentiated */
+internal fun hermiteDifferential(
+ h: Float,
+ x: Float,
+ y1: Float,
+ y2: Float,
+ t1: Float,
+ t2: Float
+): Float {
+ // The exact formula is as follows:
+ //
+ // -6 * x2 * y2 + 6 * x * y2 + 6 * x2 * y1 - 6 * x * y1 +
+ // 3 * h * t2 * x2 +
+ // 3 * h * t1 * x2 - 2 * h * t2 * x - 4 * h * t1 * x + h * t1
+ //
+ // The code below is equivalent but factored to go from 33 down to 19 instructions
+ // on aarch64 devices
+ val x2 = x * x
+ return h * (t1 - 2 * x * (2 * t1 + t2) + 3 * (t1 + t2) * x2) - 6 * (x - x2) * (y1 - y2)
}
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SpringEstimation.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SpringEstimation.kt
index 697eca6..12b230a 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SpringEstimation.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SpringEstimation.kt
@@ -17,6 +17,7 @@
package androidx.compose.animation.core
import androidx.annotation.RestrictTo
+import androidx.compose.ui.util.fastIsFinite
import kotlin.math.abs
import kotlin.math.exp
import kotlin.math.ln
@@ -67,12 +68,17 @@
// Compute the roots of the polynomial [a]x^2+[b]x+[c]=0 which may be complex.
// Here a is set to the constant 1.0, and folded into the other computations
val partialRoot = dampingCoefficient * dampingCoefficient - 4.0 * stiffness
- val firstRoot = (-dampingCoefficient + complexSqrt(partialRoot)) * 0.5
- val secondRoot = (-dampingCoefficient - complexSqrt(partialRoot)) * 0.5
+ val partialRootReal = if (partialRoot < 0.0) 0.0 else sqrt(partialRoot)
+ val partialRootImaginary = if (partialRoot < 0.0) sqrt(abs(partialRoot)) else 0.0
+
+ val firstRootReal = (-dampingCoefficient + partialRootReal) * 0.5
+ val firstRootImaginary = partialRootImaginary * 0.5
+ val secondRootReal = (-dampingCoefficient - partialRootReal) * 0.5
return estimateDurationInternal(
- firstRoot,
- secondRoot,
+ firstRootReal,
+ firstRootImaginary,
+ secondRootReal,
dampingRatio,
initialVelocity,
initialDisplacement,
@@ -96,12 +102,17 @@
// Compute the roots of the polynomial [a]x^2+[b]x+[c]=0 which may be complex.
val partialRoot = dampingCoefficient * dampingCoefficient - 4.0 * mass * springConstant
val divisor = 1.0 / (2.0 * mass)
- val firstRoot = (-dampingCoefficient + complexSqrt(partialRoot)) * divisor
- val secondRoot = (-dampingCoefficient - complexSqrt(partialRoot)) * divisor
+ val partialRootReal = if (partialRoot < 0.0) 0.0 else sqrt(partialRoot)
+ val partialRootImaginary = if (partialRoot < 0.0) sqrt(abs(partialRoot)) else 0.0
+
+ val firstRootReal = (-dampingCoefficient + partialRootReal) * divisor
+ val firstRootImaginary = partialRootImaginary * divisor
+ val secondRootReal = (-dampingCoefficient - partialRootReal) * divisor
return estimateDurationInternal(
- firstRoot,
- secondRoot,
+ firstRootReal,
+ firstRootImaginary,
+ secondRootReal,
dampingRatio,
initialVelocity,
initialDisplacement,
@@ -115,14 +126,15 @@
* c*e^(r*t)*cos(...) where c*e^(r*t) is the envelope of x(t)
*/
private fun estimateUnderDamped(
- firstRoot: ComplexDouble,
+ firstRootReal: Double,
+ firstRootImaginary: Double,
p0: Double,
v0: Double,
delta: Double
): Double {
- val r = firstRoot.real
+ val r = firstRootReal
val c1 = p0
- val c2 = (v0 - r * c1) / firstRoot.imaginary
+ val c2 = (v0 - r * c1) / firstRootImaginary
val c = sqrt(c1 * c1 + c2 * c2)
return ln(delta / c) / r
@@ -133,12 +145,12 @@
* equation x(t) = c_1*e^(r*t) + c_2*t*e^(r*t)
*/
private fun estimateCriticallyDamped(
- firstRoot: ComplexDouble,
+ firstRootReal: Double,
p0: Double,
v0: Double,
delta: Double
): Double {
- val r = firstRoot.real
+ val r = firstRootReal
val c1 = p0
val c2 = v0 - r * c1
@@ -214,14 +226,14 @@
* equation x(t) = c_1*e^(r_1*t) + c_2*e^(r_2*t)
*/
private fun estimateOverDamped(
- firstRoot: ComplexDouble,
- secondRoot: ComplexDouble,
+ firstRootReal: Double,
+ secondRootReal: Double,
p0: Double,
v0: Double,
delta: Double
): Double {
- val r1 = firstRoot.real
- val r2 = secondRoot.real
+ val r1 = firstRootReal
+ val r2 = secondRootReal
val c2 = (r1 * p0 - v0) / (r1 - r2)
val c1 = p0 - c2
@@ -293,8 +305,9 @@
// Applies Newton-Raphson's method to solve for the estimated time the spring mass system will
// last be at [delta].
private fun estimateDurationInternal(
- firstRoot: ComplexDouble,
- secondRoot: ComplexDouble,
+ firstRootReal: Double,
+ firstRootImaginary: Double,
+ secondRootReal: Double,
dampingRatio: Double,
initialVelocity: Double,
initialPosition: Double,
@@ -309,16 +322,16 @@
return (when {
dampingRatio > 1.0 ->
- estimateOverDamped(
- firstRoot = firstRoot,
- secondRoot = secondRoot,
+ estimateOverDamped(firstRootReal, secondRootReal, p0 = p0, v0 = v0, delta = delta)
+ dampingRatio < 1.0 ->
+ estimateUnderDamped(
+ firstRootReal,
+ firstRootImaginary,
v0 = v0,
p0 = p0,
delta = delta
)
- dampingRatio < 1.0 ->
- estimateUnderDamped(firstRoot = firstRoot, v0 = v0, p0 = p0, delta = delta)
- else -> estimateCriticallyDamped(firstRoot = firstRoot, v0 = v0, p0 = p0, delta = delta)
+ else -> estimateCriticallyDamped(firstRootReal, p0 = p0, v0 = v0, delta = delta)
} * 1000.0)
.toLong()
}
@@ -331,4 +344,4 @@
return x - fn(x) / fnPrime(x)
}
-@Suppress("NOTHING_TO_INLINE") private inline fun Double.isNotFinite() = !isFinite()
+@Suppress("NOTHING_TO_INLINE") private inline fun Double.isNotFinite() = !fastIsFinite()
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SpringSimulation.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SpringSimulation.kt
index f97c75f..e35eff2 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SpringSimulation.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SpringSimulation.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
+
package androidx.compose.animation.core
import androidx.compose.ui.util.packFloats
@@ -24,6 +26,17 @@
import kotlin.math.sin
import kotlin.math.sqrt
[email protected]
+internal value class Motion(val packedValue: Long) {
+ inline val value: Float
+ get() = unpackFloat1(packedValue)
+
+ inline val velocity: Float
+ get() = unpackFloat2(packedValue)
+}
+
+internal inline fun Motion(value: Float, velocity: Float) = Motion(packFloats(value, velocity))
+
/**
* Spring Simulation simulates spring physics, and allows you to query the motion (i.e. value and
* velocity) at certain time in the future based on the starting velocity and value.
@@ -39,52 +52,17 @@
* under-damped), the mass tends to overshoot, and return, and overshoot again. Without any damping
* (i.e. damping ratio = 0), the mass will oscillate forever.
*/
[email protected]
-internal value class Motion(val packedValue: Long) {
- val value: Float
- get() = unpackFloat1(packedValue)
-
- val velocity: Float
- get() = unpackFloat2(packedValue)
-
- /**
- * Returns a copy of this Motion instance optionally overriding the value or velocity parameters
- */
- fun copy(value: Float = this.value, velocity: Float = this.velocity) = Motion(value, velocity)
-}
-
-internal fun Motion(value: Float, velocity: Float) = Motion(packFloats(value, velocity))
-
-// This multiplier is used to calculate the velocity threshold given a certain value threshold.
-// The idea is that if it takes >= 1 frame to move the value threshold amount, then the velocity
-// is a reasonable threshold.
-private const val VelocityThresholdMultiplier = 1000.0 / 16.0
-
-// Value to indicate an unset state.
-internal val UNSET = Float.MAX_VALUE
-
internal class SpringSimulation(var finalPosition: Float) {
-
// Natural frequency
private var naturalFreq = sqrt(Spring.StiffnessVeryLow.toDouble())
- // Indicates whether the spring has been initialized
- private var initialized = false
-
- // Intermediate values to simplify the spring function calculation per frame.
- private var gammaPlus: Double = 0.0
- private var gammaMinus: Double = 0.0
- private var dampedFreq: Double = 0.0
-
/** Stiffness of the spring. */
var stiffness: Float
set(value) {
if (stiffness <= 0) {
- throw IllegalArgumentException("Spring stiffness constant must be positive.")
+ throwIllegalArgumentException("Spring stiffness constant must be positive.")
}
naturalFreq = sqrt(value.toDouble())
- // All the intermediate values need to be recalculated.
- initialized = false
}
get() {
return (naturalFreq * naturalFreq).toFloat()
@@ -98,11 +76,9 @@
var dampingRatio: Float = Spring.DampingRatioNoBouncy
set(value) {
if (value < 0) {
- throw IllegalArgumentException("Damping ratio must be non-negative")
+ throwIllegalArgumentException("Damping ratio must be non-negative")
}
field = value
- // All the intermediate values need to be recalculated.
- initialized = false
}
/** ********************* Below are private APIs */
@@ -116,37 +92,6 @@
}
/**
- * Initialize the string by doing the necessary pre-calculation as well as some validity check
- * on the setup.
- *
- * @throws IllegalStateException if the final position is not yet set by the time the spring
- * animation has started
- */
- private fun init() {
- if (initialized) {
- return
- }
-
- if (finalPosition == UNSET) {
- throw IllegalStateException(
- "Error: Final position of the spring must be set before the animation starts"
- )
- }
-
- val dampingRatioSquared = dampingRatio * dampingRatio.toDouble()
- if (dampingRatio > 1) {
- // Over damping
- gammaPlus = (-dampingRatio * naturalFreq + naturalFreq * sqrt(dampingRatioSquared - 1))
- gammaMinus = (-dampingRatio * naturalFreq - naturalFreq * sqrt(dampingRatioSquared - 1))
- } else if (dampingRatio >= 0 && dampingRatio < 1) {
- // Under damping
- dampedFreq = naturalFreq * sqrt(1 - dampingRatioSquared)
- }
-
- initialized = true
- }
-
- /**
* Internal only call for Spring to calculate the spring position/velocity using an analytical
* approach.
*/
@@ -155,19 +100,24 @@
lastVelocity: Float,
timeElapsed: Long
): Motion {
- init()
-
val adjustedDisplacement = lastDisplacement - finalPosition
val deltaT = timeElapsed / 1000.0 // unit: seconds
+ val dampingRatioSquared = dampingRatio * dampingRatio.toDouble()
+ val r = -dampingRatio * naturalFreq
+
val displacement: Double
val currentVelocity: Double
+
if (dampingRatio > 1) {
+ // Over damping
+ val s = naturalFreq * sqrt(dampingRatioSquared - 1)
+ val gammaPlus = r + s
+ val gammaMinus = r - s
+
// Overdamped
- val coeffA =
- (adjustedDisplacement -
- ((gammaMinus * adjustedDisplacement - lastVelocity) / (gammaMinus - gammaPlus)))
val coeffB =
- ((gammaMinus * adjustedDisplacement - lastVelocity) / (gammaMinus - gammaPlus))
+ (gammaMinus * adjustedDisplacement - lastVelocity) / (gammaMinus - gammaPlus)
+ val coeffA = adjustedDisplacement - coeffB
displacement = (coeffA * exp(gammaMinus * deltaT) + coeffB * exp(gammaPlus * deltaT))
currentVelocity =
(coeffA * gammaMinus * exp(gammaMinus * deltaT) +
@@ -176,24 +126,21 @@
// Critically damped
val coeffA = adjustedDisplacement
val coeffB = lastVelocity + naturalFreq * adjustedDisplacement
- displacement = (coeffA + coeffB * deltaT) * exp(-naturalFreq * deltaT)
+ val nFdT = -naturalFreq * deltaT
+ displacement = (coeffA + coeffB * deltaT) * exp(nFdT)
currentVelocity =
- (((coeffA + coeffB * deltaT) * exp(-naturalFreq * deltaT) * (-naturalFreq)) +
- coeffB * exp(-naturalFreq * deltaT))
+ (((coeffA + coeffB * deltaT) * exp(nFdT) * (-naturalFreq)) + coeffB * exp(nFdT))
} else {
+ val dampedFreq = naturalFreq * sqrt(1 - dampingRatioSquared)
// Underdamped
val cosCoeff = adjustedDisplacement
- val sinCoeff =
- ((1 / dampedFreq) *
- (((dampingRatio * naturalFreq * adjustedDisplacement) + lastVelocity)))
- displacement =
- (exp(-dampingRatio * naturalFreq * deltaT) *
- ((cosCoeff * cos(dampedFreq * deltaT) + sinCoeff * sin(dampedFreq * deltaT))))
+ val sinCoeff = ((1 / dampedFreq) * (((-r * adjustedDisplacement) + lastVelocity)))
+ val dFdT = dampedFreq * deltaT
+ displacement = (exp(r * deltaT) * ((cosCoeff * cos(dFdT) + sinCoeff * sin(dFdT))))
currentVelocity =
- (displacement * (-naturalFreq) * dampingRatio +
- (exp(-dampingRatio * naturalFreq * deltaT) *
- ((-dampedFreq * cosCoeff * sin(dampedFreq * deltaT) +
- dampedFreq * sinCoeff * cos(dampedFreq * deltaT)))))
+ (displacement * r +
+ (exp(r * deltaT) *
+ ((-dampedFreq * cosCoeff * sin(dFdT) + dampedFreq * sinCoeff * cos(dFdT)))))
}
val newValue = (displacement + finalPosition).toFloat()
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
index b61ed67..9ceaf13 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
@@ -707,7 +707,7 @@
animation.animationSpecDuration =
((1.0 - animation.start[0]) * totalDurationNanos).roundToLong()
}
- } else {
+ } else if (totalDurationNanos != 0L) {
// seekTo() called with a fraction. If an animation is running, we can just wait
// for the animation to change the value. The fraction may not be the best way
// to advance a regular animation.
@@ -1416,7 +1416,7 @@
init {
val visibilityThreshold: T? =
- visibilityThresholdMap.get(typeConverter)?.let {
+ VisibilityThresholdMap.get(typeConverter)?.let {
val vector = typeConverter.convertToVector(initialValue)
for (id in 0 until vector.size) {
vector[id] = it
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorConverters.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorConverters.kt
index 75b4aba..60c04cf 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorConverters.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorConverters.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
+
package androidx.compose.animation.core
import androidx.compose.ui.geometry.Offset
@@ -24,6 +26,7 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastCoerceAtLeast
import androidx.compose.ui.util.fastRoundToInt
/**
@@ -64,7 +67,7 @@
override val convertFromVector: (V) -> T
) : TwoWayConverter<T, V>
-internal fun lerp(start: Float, stop: Float, fraction: Float) =
+internal inline fun lerp(start: Float, stop: Float, fraction: Float) =
(start * (1 - fraction) + stop * fraction)
/** A [TwoWayConverter] that converts [Float] from and to [AnimationVector1D] */
@@ -157,8 +160,8 @@
{ AnimationVector2D(it.width.toFloat(), it.height.toFloat()) },
{
IntSize(
- width = it.v1.fastRoundToInt().coerceAtLeast(0),
- height = it.v2.fastRoundToInt().coerceAtLeast(0)
+ width = it.v1.fastRoundToInt().fastCoerceAtLeast(0),
+ height = it.v2.fastRoundToInt().fastCoerceAtLeast(0)
)
}
)
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
index d2e83ca..54f125b 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
@@ -22,6 +22,7 @@
import androidx.collection.MutableIntObjectMap
import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
import androidx.compose.animation.core.internal.JvmDefaultWithCompatibility
+import androidx.compose.ui.util.fastCoerceIn
import kotlin.jvm.JvmInline
import kotlin.math.min
@@ -183,7 +184,7 @@
* [VectorizedDurationBasedAnimationSpec].
*/
internal fun VectorizedDurationBasedAnimationSpec<*>.clampPlayTime(playTime: Long): Long {
- return (playTime - delayMillis).coerceIn(0, durationMillis.toLong())
+ return (playTime - delayMillis).fastCoerceIn(0, durationMillis.toLong())
}
/**
@@ -260,7 +261,7 @@
VectorizedKeyframeSpecElementInfo(
vectorValue = valueEasing.first,
easing = valueEasing.second,
- arcMode = ArcMode.Companion.ArcLinear
+ arcMode = ArcMode.ArcLinear
)
}
@@ -269,7 +270,7 @@
durationMillis = durationMillis,
delayMillis = delayMillis,
defaultEasing = LinearEasing,
- initialArcMode = ArcMode.Companion.ArcLinear
+ initialArcMode = ArcMode.ArcLinear
)
/**
@@ -277,23 +278,23 @@
*
* This will be used to do a faster lookup for the corresponding Easing curves.
*/
- private lateinit var modes: IntArray
- private lateinit var times: FloatArray
- private lateinit var valueVector: V
- private lateinit var velocityVector: V
+ private var modes: IntArray = EmptyIntArray
+ private var times: FloatArray = EmptyFloatArray
+ private var valueVector: V? = null
+ private var velocityVector: V? = null
// Objects for ArcSpline
- private lateinit var lastInitialValue: V
- private lateinit var lastTargetValue: V
- private lateinit var posArray: FloatArray
- private lateinit var slopeArray: FloatArray
- private lateinit var arcSpline: ArcSpline
+ private var lastInitialValue: V? = null
+ private var lastTargetValue: V? = null
+ private var posArray: FloatArray = EmptyFloatArray
+ private var slopeArray: FloatArray = EmptyFloatArray
+ private var arcSpline: ArcSpline = EmptyArcSpline
private fun init(initialValue: V, targetValue: V, initialVelocity: V) {
- var requiresArcSpline = ::arcSpline.isInitialized
+ var requiresArcSpline = arcSpline !== EmptyArcSpline
// Only need to initialize once
- if (!::valueVector.isInitialized) {
+ if (valueVector == null) {
valueVector = initialValue.newInstance()
velocityVector = initialVelocity.newInstance()
@@ -302,7 +303,7 @@
modes =
IntArray(timestamps.size) {
val mode = (keyframes[timestamps[it]]?.arcMode ?: initialArcMode)
- if (mode != ArcMode.Companion.ArcLinear) {
+ if (mode != ArcMode.ArcLinear) {
requiresArcSpline = true
}
@@ -316,7 +317,7 @@
// Initialize variables dependent on initial and/or target value
if (
- !::arcSpline.isInitialized ||
+ arcSpline === EmptyArcSpline ||
lastInitialValue != initialValue ||
lastTargetValue != targetValue
) {
@@ -332,26 +333,18 @@
// may change, and only if the keyframes does not overwrite it
val values =
Array(timestamps.size) {
- when (val timestamp = timestamps[it]) {
- // Start (zero) and end (durationMillis) may not have been declared in
- // keyframes
- 0 -> {
- if (!keyframes.contains(timestamp)) {
- FloatArray(dimensionCount, initialValue::get)
- } else {
- FloatArray(dimensionCount, keyframes[timestamp]!!.vectorValue::get)
- }
- }
- durationMillis -> {
- if (!keyframes.contains(timestamp)) {
- FloatArray(dimensionCount, targetValue::get)
- } else {
- FloatArray(dimensionCount, keyframes[timestamp]!!.vectorValue::get)
- }
- }
-
+ val timestamp = timestamps[it]
+ val info = keyframes[timestamp]
+ // Start (zero) and end (durationMillis) may not have been declared in
+ // keyframes
+ if (timestamp == 0 && info == null) {
+ FloatArray(dimensionCount) { i -> initialValue[i] }
+ } else if (timestamp == durationMillis && info == null) {
+ FloatArray(dimensionCount) { i -> targetValue[i] }
+ } else {
// All other values are guaranteed to exist
- else -> FloatArray(dimensionCount, keyframes[timestamp]!!.vectorValue::get)
+ val vectorValue = info!!.vectorValue
+ FloatArray(dimensionCount) { i -> vectorValue[i] }
}
}
arcSpline = ArcSpline(arcModes = modes, timePoints = times, y = values)
@@ -372,21 +365,28 @@
val clampedPlayTime = clampPlayTime(playTimeMillis).toInt()
// If there is a key frame defined with the given time stamp, return that value
- if (keyframes.contains(clampedPlayTime)) {
- return keyframes[clampedPlayTime]!!.vectorValue
+ val keyframe = keyframes[clampedPlayTime]
+ if (keyframe != null) {
+ return keyframe.vectorValue
}
if (clampedPlayTime >= durationMillis) {
return targetValue
- } else if (clampedPlayTime <= 0) return initialValue
+ } else if (clampedPlayTime <= 0) {
+ return initialValue
+ }
init(initialValue, targetValue, initialVelocity)
+ // Cannot be null after calling init()
+ val valueVector = valueVector!!
+
// ArcSpline is only initialized when necessary
- if (::arcSpline.isInitialized) {
+ if (arcSpline !== EmptyArcSpline) {
// ArcSpline requires eased play time in seconds
val easedTime = getEasedTime(clampedPlayTime)
+ val posArray = posArray
arcSpline.getPos(time = easedTime, v = posArray)
for (i in posArray.indices) {
valueVector[i] = posArray[i]
@@ -401,28 +401,18 @@
val easedTime = getEasedTimeFromIndex(index, clampedPlayTime, true)
val timestampStart = timestamps[index]
- val startValue: V =
- if (keyframes.contains(timestampStart)) {
- keyframes[timestampStart]!!.vectorValue
- } else {
- // Use initial value if it wasn't overwritten by the user
- // This is always the correct fallback assuming timestamps and keyframes were
- // populated
- // as expected
- initialValue
- }
+ val startKeyframe = keyframes[timestampStart]
+ // Use initial value if it wasn't overwritten by the user
+ // This is always the correct fallback assuming timestamps and keyframes were populated
+ // as expected
+ val startValue: V = startKeyframe?.vectorValue ?: initialValue
val timestampEnd = timestamps[index + 1]
- val endValue =
- if (keyframes.contains(timestampEnd)) {
- keyframes[timestampEnd]!!.vectorValue
- } else {
- // Use target value if it wasn't overwritten by the user
- // This is always the correct fallback assuming timestamps and keyframes were
- // populated
- // as expected
- targetValue
- }
+ val endKeyframe = keyframes[timestampEnd]
+ // Use target value if it wasn't overwritten by the user
+ // This is always the correct fallback assuming timestamps and keyframes were populated
+ // as expected
+ val endValue: V = endKeyframe?.vectorValue ?: targetValue
for (i in 0 until valueVector.size) {
valueVector[i] = lerp(startValue[i], endValue[i], easedTime)
@@ -444,9 +434,13 @@
init(initialValue, targetValue, initialVelocity)
+ // Cannot be null after calling init()
+ val velocityVector = velocityVector!!
+
// ArcSpline is only initialized when necessary
- if (::arcSpline.isInitialized) {
+ if (arcSpline !== EmptyArcSpline) {
val easedTime = getEasedTime(clampedPlayTime.toInt())
+ val slopeArray = slopeArray
arcSpline.getSlope(time = easedTime, v = slopeArray)
for (i in slopeArray.indices) {
velocityVector[i] = slopeArray[i]
@@ -505,7 +499,6 @@
}
}
-@OptIn(ExperimentalAnimationSpecApi::class)
internal data class VectorizedKeyframeSpecElementInfo<V : AnimationVector>(
val vectorValue: V,
val easing: Easing,
@@ -528,20 +521,20 @@
* Interpolates using a quarter of an Ellipse where the curve is "above" the center of the
* Ellipse.
*/
- public val ArcAbove: ArcMode = ArcMode(ArcSpline.ArcAbove)
+ public val ArcAbove: ArcMode = ArcMode(ArcSplineArcAbove)
/**
* Interpolates using a quarter of an Ellipse where the curve is "below" the center of the
* Ellipse.
*/
- public val ArcBelow: ArcMode = ArcMode(ArcSpline.ArcBelow)
+ public val ArcBelow: ArcMode = ArcMode(ArcSplineArcBelow)
/**
* An [ArcMode] that forces linear interpolation.
*
* You'll likely only use this mode within a keyframe.
*/
- public val ArcLinear: ArcMode = ArcMode(ArcSpline.ArcStartLinear)
+ public val ArcLinear: ArcMode = ArcMode(ArcSplineArcStartLinear)
}
}
@@ -560,10 +553,10 @@
targetValue: V,
initialVelocity: V
): V {
- if (playTimeNanos < delayMillis * MillisToNanos) {
- return initialValue
+ return if (playTimeNanos < delayMillis * MillisToNanos) {
+ initialValue
} else {
- return targetValue
+ targetValue
}
}
@@ -580,8 +573,6 @@
get() = 0
}
-private const val InfiniteIterations: Int = Int.MAX_VALUE
-
/**
* This animation takes another [VectorizedDurationBasedAnimationSpec] and plays it __infinite__
* times.
@@ -742,10 +733,10 @@
} else {
val postOffsetPlayTimeNanos = playTimeNanos + initialOffsetNanos
val repeatsCount = min(postOffsetPlayTimeNanos / durationNanos, iterations - 1L)
- if (repeatMode == RepeatMode.Restart || repeatsCount % 2 == 0L) {
- return postOffsetPlayTimeNanos - repeatsCount * durationNanos
+ return if (repeatMode == RepeatMode.Restart || repeatsCount % 2 == 0L) {
+ postOffsetPlayTimeNanos - repeatsCount * durationNanos
} else {
- return (repeatsCount + 1) * durationNanos - postOffsetPlayTimeNanos
+ (repeatsCount + 1) * durationNanos - postOffsetPlayTimeNanos
}
}
}
@@ -861,7 +852,7 @@
public val dampingRatio: Float,
public val stiffness: Float,
anims: Animations
-) : VectorizedFiniteAnimationSpec<V> by VectorizedFloatAnimationSpec<V>(anims) {
+) : VectorizedFiniteAnimationSpec<V> by VectorizedFloatAnimationSpec(anims) {
/**
* Creates a [VectorizedSpringSpec] that uses the same spring constants (i.e. [dampingRatio] and
@@ -889,17 +880,17 @@
dampingRatio: Float,
stiffness: Float
): Animations {
- if (visibilityThreshold != null) {
- return object : Animations {
+ return if (visibilityThreshold != null) {
+ object : Animations {
private val anims =
- (0 until visibilityThreshold.size).map { index ->
+ Array(visibilityThreshold.size) { index ->
FloatSpringSpec(dampingRatio, stiffness, visibilityThreshold[index])
}
override fun get(index: Int): FloatSpringSpec = anims[index]
}
} else {
- return object : Animations {
+ object : Animations {
private val anim = FloatSpringSpec(dampingRatio, stiffness)
override fun get(index: Int): FloatSpringSpec = anim
@@ -1029,17 +1020,18 @@
@Suppress("MethodNameUnits")
override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long {
var maxDuration = 0L
- (0 until initialValue.size).forEach {
+ for (i in 0 until initialValue.size) {
maxDuration =
maxOf(
maxDuration,
- anims[it].getDurationNanos(
- initialValue[it],
- targetValue[it],
- initialVelocity[it]
- )
+ anims[i].getDurationNanos(initialValue[i], targetValue[i], initialVelocity[i])
)
}
return maxDuration
}
}
+
+private val EmptyIntArray: IntArray = IntArray(0)
+private val EmptyFloatArray: FloatArray = FloatArray(0)
+private val EmptyArcSpline =
+ ArcSpline(IntArray(2), FloatArray(2), arrayOf(FloatArray(2), FloatArray(2)))
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt
index ddc9938..b92b34a 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt
@@ -28,22 +28,22 @@
private val periodicBias: Float,
) : VectorizedDurationBasedAnimationSpec<V> {
// Objects initialized lazily once
- private lateinit var valueVector: V
- private lateinit var velocityVector: V
+ private var valueVector: V? = null
+ private var velocityVector: V? = null
// Time values passed to MonoSpline.
private lateinit var times: FloatArray
// Objects for MonoSpline
- private lateinit var monoSpline: MonoSpline
+ private var monoSpline: MonoSpline? = null
// [values] are not modified by MonoSpline so we can safely re-use it to re-instantiate it
- private lateinit var values: Array<FloatArray>
+ private var values: Array<FloatArray>? = null
private var lastInitialValue: V? = null
private var lastTargetValue: V? = null
private fun init(initialValue: V, targetValue: V, initialVelocity: V) {
// Only need to initialize once
- if (!::valueVector.isInitialized) {
+ if (valueVector == null) {
valueVector = initialValue.newInstance()
velocityVector = initialVelocity.newInstance()
@@ -52,9 +52,7 @@
// Need to re-initialize based on initial/target values
if (
- !::monoSpline.isInitialized ||
- lastInitialValue != initialValue ||
- lastTargetValue != targetValue
+ monoSpline == null || lastInitialValue != initialValue || lastTargetValue != targetValue
) {
val initialChanged = lastInitialValue != initialValue
val targetChanged = lastTargetValue != targetValue
@@ -63,39 +61,31 @@
val dimension = initialValue.size
- if (!::values.isInitialized) {
+ var values = values
+ if (values == null) {
values =
Array(timestamps.size) {
- when (val timestamp = timestamps[it]) {
- // Start (zero) and end (durationMillis) may not have been declared in
- // keyframes
- 0 -> {
- if (!keyframes.contains(timestamp)) {
- FloatArray(dimension, initialValue::get)
- } else {
- FloatArray(dimension, keyframes[timestamp]!!.first::get)
- }
- }
- durationMillis -> {
- if (!keyframes.contains(timestamp)) {
- FloatArray(dimension, targetValue::get)
- } else {
- FloatArray(dimension, keyframes[timestamp]!!.first::get)
- }
- }
-
- // All other values are guaranteed to exist
- else -> FloatArray(dimension, keyframes[timestamp]!!.first::get)
+ val timestamp = timestamps[it]
+ val keyframe = keyframes[timestamp]
+ if (timestamp == 0 && keyframe == null) {
+ FloatArray(dimension) { i -> initialValue[i] }
+ } else if (timestamp == durationMillis && keyframe == null) {
+ FloatArray(dimension) { i -> targetValue[i] }
+ } else {
+ val vectorValue = keyframe!!.first
+ FloatArray(dimension) { i -> vectorValue[i] }
}
}
+ this.values = values
} else {
// We can re-use most of the objects. Only the start and end may need to be replaced
if (initialChanged && !keyframes.contains(0)) {
- values[timestamps.binarySearch(0)] = FloatArray(dimension, initialValue::get)
+ val index = timestamps.binarySearch(0)
+ values[index] = FloatArray(dimension) { i -> initialValue[i] }
}
if (targetChanged && !keyframes.contains(durationMillis)) {
- values[timestamps.binarySearch(durationMillis)] =
- FloatArray(dimension, targetValue::get)
+ val index = timestamps.binarySearch(durationMillis)
+ values[index] = FloatArray(dimension) { i -> targetValue[i] }
}
}
monoSpline = MonoSpline(times, values, periodicBias)
@@ -111,13 +101,16 @@
val playTimeMillis = playTimeNanos / MillisToNanos
val clampedPlayTime = clampPlayTime(playTimeMillis).toInt()
// If there is a key frame defined with the given time stamp, return that value
- if (keyframes.containsKey(clampedPlayTime)) {
- return keyframes[clampedPlayTime]!!.first
+ val keyframe = keyframes[clampedPlayTime]
+ if (keyframe != null) {
+ return keyframe.first
}
if (clampedPlayTime >= durationMillis) {
return targetValue
- } else if (clampedPlayTime <= 0) return initialValue
+ } else if (clampedPlayTime <= 0) {
+ return initialValue
+ }
init(initialValue, targetValue, initialVelocity)
@@ -125,7 +118,8 @@
// time range at every call
val index = findEntryForTimeMillis(clampedPlayTime)
- monoSpline.getPos(
+ val valueVector = valueVector!!
+ monoSpline!!.getPos(
index = index,
time = getEasedTimeFromIndex(index, clampedPlayTime),
v = valueVector
@@ -141,9 +135,6 @@
): V {
val playTimeMillis = playTimeNanos / MillisToNanos
val clampedPlayTime = clampPlayTime(playTimeMillis).toInt()
- if (clampedPlayTime < 0) {
- return initialVelocity
- }
init(initialValue, targetValue, initialVelocity)
@@ -151,7 +142,8 @@
// time range at every call
val index = findEntryForTimeMillis(clampedPlayTime)
- monoSpline.getSlope(
+ val velocityVector = velocityVector!!
+ monoSpline!!.getSlope(
index = index,
time = getEasedTimeFromIndex(index, clampedPlayTime),
v = velocityVector
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
index 8852c65..6920792 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
@@ -28,7 +28,7 @@
private const val DpVisibilityThreshold = 0.1f
private const val PxVisibilityThreshold = 0.5f
-private val rectVisibilityThreshold =
+private val RectVisibilityThreshold =
Rect(PxVisibilityThreshold, PxVisibilityThreshold, PxVisibilityThreshold, PxVisibilityThreshold)
/**
@@ -93,11 +93,14 @@
* to stop when the value is close enough to the target.
*/
public val Rect.Companion.VisibilityThreshold: Rect
- get() = rectVisibilityThreshold
+ get() = RectVisibilityThreshold
// TODO: Add Dp.DefaultAnimation = spring<Dp>(visibilityThreshold = Dp.VisibilityThreshold)
-
-internal val visibilityThresholdMap: Map<TwoWayConverter<*, *>, Float> =
+// The floats coming out of this map are fed to APIs that expect objects (generics), so it's
+// better to store them as boxed floats here instead of causing unboxing/boxing every time
+// the values are read out and forwarded to other APIs
+@Suppress("PrimitiveInCollection")
+internal val VisibilityThresholdMap: Map<TwoWayConverter<*, *>, Float> =
mapOf(
Int.VectorConverter to 1f,
IntSize.VectorConverter to 1f,
diff --git a/compose/animation/animation/api/current.txt b/compose/animation/animation/api/current.txt
index e3b1efc..05c53bd 100644
--- a/compose/animation/animation/api/current.txt
+++ b/compose/animation/animation/api/current.txt
@@ -5,6 +5,10 @@
method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> defaultDecayAnimationSpec();
}
+ public final class AnimateBoundsModifierKt {
+ method @SuppressCompatibility @androidx.compose.animation.ExperimentalSharedTransitionApi public static androidx.compose.ui.Modifier animateBounds(androidx.compose.ui.Modifier, androidx.compose.ui.layout.LookaheadScope lookaheadScope, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.BoundsTransform boundsTransform, optional boolean animateMotionFrameOfReference);
+ }
+
public final class AnimatedContentKt {
method @androidx.compose.runtime.Composable public static <S> void AnimatedContent(androidx.compose.animation.core.Transition<S>, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<? super S,? extends java.lang.Object?> contentKey, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedContentScope,? super S,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static <S> void AnimatedContent(S targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional String label, optional kotlin.jvm.functions.Function1<? super S,? extends java.lang.Object?> contentKey, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedContentScope,? super S,kotlin.Unit> content);
diff --git a/compose/animation/animation/api/restricted_current.txt b/compose/animation/animation/api/restricted_current.txt
index e3b1efc..05c53bd 100644
--- a/compose/animation/animation/api/restricted_current.txt
+++ b/compose/animation/animation/api/restricted_current.txt
@@ -5,6 +5,10 @@
method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> defaultDecayAnimationSpec();
}
+ public final class AnimateBoundsModifierKt {
+ method @SuppressCompatibility @androidx.compose.animation.ExperimentalSharedTransitionApi public static androidx.compose.ui.Modifier animateBounds(androidx.compose.ui.Modifier, androidx.compose.ui.layout.LookaheadScope lookaheadScope, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.BoundsTransform boundsTransform, optional boolean animateMotionFrameOfReference);
+ }
+
public final class AnimatedContentKt {
method @androidx.compose.runtime.Composable public static <S> void AnimatedContent(androidx.compose.animation.core.Transition<S>, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<? super S,? extends java.lang.Object?> contentKey, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedContentScope,? super S,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static <S> void AnimatedContent(S targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional String label, optional kotlin.jvm.functions.Function1<? super S,? extends java.lang.Object?> contentKey, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedContentScope,? super S,kotlin.Unit> content);
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
index c8b339c..4f00d4f 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
@@ -38,7 +38,9 @@
import androidx.compose.animation.demos.layoutanimation.ScreenTransitionDemo
import androidx.compose.animation.demos.layoutanimation.ShrineCartDemo
import androidx.compose.animation.demos.lookahead.AnimateBoundsModifierDemo
+import androidx.compose.animation.demos.lookahead.AnimateBoundsOnFloatingToolbarDemo
import androidx.compose.animation.demos.lookahead.CraneDemo
+import androidx.compose.animation.demos.lookahead.LookaheadInScrollingColumn
import androidx.compose.animation.demos.lookahead.LookaheadLayoutWithAlignmentLinesDemo
import androidx.compose.animation.demos.lookahead.LookaheadSamplesDemo
import androidx.compose.animation.demos.lookahead.LookaheadWithAnimatedContentSize
@@ -144,6 +146,10 @@
},
ComposableDemo("Lookahead With Tab Row") { LookaheadWithTabRowDemo() },
ComposableDemo("Lookahead With Scaffold") { LookaheadWithScaffold() },
+ ComposableDemo("Lookahead With Scroll") { LookaheadInScrollingColumn() },
+ ComposableDemo("Floating Toolbar w/ AnimateBounds") {
+ AnimateBoundsOnFloatingToolbarDemo()
+ },
)
),
DemoCategory(
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifier.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifier.kt
deleted file mode 100644
index 87e9e5b..0000000
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifier.kt
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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.animation.demos.lookahead
-
-import androidx.compose.animation.core.AnimationVector2D
-import androidx.compose.animation.core.DeferredTargetAnimation
-import androidx.compose.animation.core.ExperimentalAnimatableApi
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.VectorConverter
-import androidx.compose.animation.core.spring
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.draw.drawWithContent
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.layout.LookaheadScope
-import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.layout.approachLayout
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.round
-import kotlinx.coroutines.CoroutineScope
-
-context(LookaheadScope)
-@OptIn(ExperimentalAnimatableApi::class)
-fun Modifier.animateBounds(
- modifier: Modifier = Modifier,
- sizeAnimationSpec: FiniteAnimationSpec<IntSize> =
- spring(Spring.DampingRatioNoBouncy, Spring.StiffnessMediumLow),
- positionAnimationSpec: FiniteAnimationSpec<IntOffset> =
- spring(Spring.DampingRatioNoBouncy, Spring.StiffnessMediumLow),
- debug: Boolean = false,
-) = composed {
- val outerOffsetAnimation = remember { DeferredTargetAnimation(IntOffset.VectorConverter) }
- val outerSizeAnimation = remember { DeferredTargetAnimation(IntSize.VectorConverter) }
-
- val offsetAnimation = remember { DeferredTargetAnimation(IntOffset.VectorConverter) }
- val sizeAnimation = remember { DeferredTargetAnimation(IntSize.VectorConverter) }
-
- val coroutineScope = rememberCoroutineScope()
-
- // The measure logic in `approachLayout` is skipped in the lookahead pass, as
- // approachLayout is expected to produce intermediate stages of a layout transform.
- // When the measure block is invoked after lookahead pass, the lookahead size of the
- // child will be accessible as a parameter to the measure block.
- this.drawWithContent {
- drawContent()
- if (debug) {
- // val offset = outerOffsetAnimation.pendingTarget!! -
- // outerOffsetAnimation.value!!
- // translate(
- // offset.x.toFloat(), offset.y.toFloat()
- // ) {
- // drawRect(Color.Black.copy(alpha = 0.5f), style = Stroke(10f))
- // }
- }
- }
- .approachLayout(
- isMeasurementApproachInProgress = {
- outerSizeAnimation.updateTarget(it, coroutineScope, sizeAnimationSpec)
- !outerSizeAnimation.isIdle
- },
- isPlacementApproachInProgress = {
- val target = lookaheadScopeCoordinates.localLookaheadPositionOf(it)
- outerOffsetAnimation.updateTarget(
- target.round(),
- coroutineScope,
- positionAnimationSpec
- )
- !outerOffsetAnimation.isIdle
- }
- ) { measurable, constraints ->
- val (w, h) =
- outerSizeAnimation.updateTarget(
- lookaheadSize,
- coroutineScope,
- sizeAnimationSpec,
- )
- measurable.measure(constraints).run {
- layout(w, h) {
- with(coroutineScope) {
- val (x, y) =
- outerOffsetAnimation.updateTargetBasedOnCoordinates(
- positionAnimationSpec
- )
- place(x, y)
- }
- }
- }
- }
- .then(modifier)
- .drawWithContent {
- drawContent()
- if (debug) {
- // val offset = offsetAnimation.pendingTarget!! -
- // offsetAnimation.value!!
- // translate(
- // offset.x.toFloat(), offset.y.toFloat()
- // ) {
- // drawRect(Color.Green.copy(alpha = 0.5f), style = Stroke(10f))
- // }
- }
- }
- .approachLayout(
- isMeasurementApproachInProgress = {
- sizeAnimation.updateTarget(it, coroutineScope, sizeAnimationSpec)
- !sizeAnimation.isIdle
- },
- isPlacementApproachInProgress = {
- val target = lookaheadScopeCoordinates.localLookaheadPositionOf(it)
- offsetAnimation.updateTarget(target.round(), coroutineScope, positionAnimationSpec)
- !offsetAnimation.isIdle
- }
- ) { measurable, _ ->
- // When layout changes, the lookahead pass will calculate a new final size for the
- // child modifier. This lookahead size can be used to animate the size
- // change, such that the animation starts from the current size and gradually
- // change towards `lookaheadSize`.
- val (width, height) =
- sizeAnimation.updateTarget(
- lookaheadSize,
- coroutineScope,
- sizeAnimationSpec,
- )
- // Creates a fixed set of constraints using the animated size
- val animatedConstraints = Constraints.fixed(width, height)
- // Measure child/children with animated constraints.
- val placeable = measurable.measure(animatedConstraints)
- layout(placeable.width, placeable.height) {
- val (x, y) =
- with(coroutineScope) {
- offsetAnimation.updateTargetBasedOnCoordinates(
- positionAnimationSpec,
- )
- }
- placeable.place(x, y)
- }
- }
-}
-
-context(LookaheadScope, Placeable.PlacementScope, CoroutineScope)
-@OptIn(ExperimentalAnimatableApi::class)
-internal fun DeferredTargetAnimation<IntOffset, AnimationVector2D>.updateTargetBasedOnCoordinates(
- animationSpec: FiniteAnimationSpec<IntOffset>,
-): IntOffset {
- coordinates?.let { coordinates ->
- with(this@PlacementScope) {
- val targetOffset = lookaheadScopeCoordinates.localLookaheadPositionOf(coordinates)
- val animOffset =
- updateTarget(
- targetOffset.round(),
- this@CoroutineScope,
- animationSpec,
- )
- val current =
- lookaheadScopeCoordinates.localPositionOf(coordinates, Offset.Zero).round()
- return (animOffset - current)
- }
- }
-
- return IntOffset.Zero
-}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifierDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifierDemo.kt
index 5ebb842..787045d 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifierDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifierDemo.kt
@@ -16,6 +16,8 @@
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@@ -39,6 +41,7 @@
import androidx.compose.ui.unit.dp
import kotlin.random.Random
+@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun AnimateBoundsModifierDemo() {
var height by remember { mutableIntStateOf(200) }
@@ -63,18 +66,27 @@
Box(Modifier.fillMaxHeight(0.5f).fillMaxSize()) {
Box(
Modifier.background(Color.Gray)
- .animateBounds(Modifier.padding(left.dp, top.dp, right.dp, bottom.dp))
+ .animateBounds(
+ this@LookaheadScope,
+ Modifier.padding(left.dp, top.dp, right.dp, bottom.dp)
+ )
.background(Color.Red)
.fillMaxSize()
)
}
Row(Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) {
Box(
- Modifier.animateBounds(Modifier.weight(weight).height(height.dp))
+ Modifier.animateBounds(
+ this@LookaheadScope,
+ Modifier.weight(weight).height(height.dp)
+ )
.background(Color(0xffa2d2ff), RoundedCornerShape(5.dp))
)
Box(
- Modifier.animateBounds(Modifier.weight(1f).height(height.dp))
+ Modifier.animateBounds(
+ this@LookaheadScope,
+ Modifier.weight(1f).height(height.dp)
+ )
.background(Color(0xfffff3b0))
)
}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsOnFloatingToolbar.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsOnFloatingToolbar.kt
new file mode 100644
index 0000000..0221499
--- /dev/null
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsOnFloatingToolbar.kt
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2024 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.animation.demos.lookahead
+
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.demos.visualaid.EasingItemDemo
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Edit
+import androidx.compose.material.icons.outlined.FavoriteBorder
+import androidx.compose.material.icons.outlined.Share
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentWithReceiverOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.LookaheadScope
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.coerceAtLeast
+import androidx.compose.ui.unit.dp
+
+/**
+ * Example using [animateBounds] with nested movable content.
+ *
+ * Animates an Icon component from a Toolbar to a FAB position, the toolbar is also animated to hide
+ * it under the FAB.
+ */
+@Preview
+@Composable
+fun AnimateBoundsOnFloatingToolbarDemo() {
+ Box(Modifier.fillMaxSize()) {
+ Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) {
+ val sampleText = remember { LoremIpsum().values.first() }
+ Text(
+ text = "Click on the Toolbar to animate",
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.h6
+ )
+ Text(text = sampleText)
+ }
+ FloatingFabToolbar(
+ Modifier.align(Alignment.BottomCenter)
+ .fillMaxWidth()
+ .padding(8.dp)
+ .padding(bottom = 24.dp)
+ )
+ }
+}
+
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Composable
+private fun FloatingFabToolbar(modifier: Modifier = Modifier) {
+ var mode by remember { mutableStateOf(FabToolbarMode.Toolbar) }
+
+ val animationDuration = 600
+ val animEasing = EasingItemDemo.EmphasizedEasing.function
+
+ val editIconPadding by
+ animateDpAsState(
+ targetValue = if (mode == FabToolbarMode.Fab) 12.dp else 0.dp,
+ animationSpec = tween(animationDuration, easing = animEasing),
+ label = "Edit Icon Padding"
+ )
+
+ val myEditIcon = remember {
+ movableContentWithReceiverOf<LookaheadScope, Modifier> { iconModifier ->
+ Box(
+ modifier =
+ iconModifier
+ .let {
+ if (mode == FabToolbarMode.Toolbar) {
+ it.fillMaxSize()
+ } else {
+ it.aspectRatio(1f, matchHeightConstraintsFirst = true)
+ }
+ }
+ .animateBounds(
+ lookaheadScope = this,
+ modifier = Modifier,
+ boundsTransform = { _, _ ->
+ tween(animationDuration, easing = animEasing)
+ },
+ ),
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Edit,
+ contentDescription = null,
+ tint = MaterialTheme.colors.onPrimary,
+ modifier =
+ Modifier.background(MaterialTheme.colors.primary, RoundedCornerShape(16.dp))
+ .fillMaxSize()
+ .padding(editIconPadding.coerceAtLeast(0.dp))
+ )
+ }
+ }
+ }
+
+ // Toolbar container + Toolbar
+ val myToolbar = remember {
+ movableContentWithReceiverOf<LookaheadScope, Modifier> { toolbarMod ->
+ // Toolbar container
+ Box(
+ modifier =
+ toolbarMod
+ .animateBounds(
+ lookaheadScope = this,
+ boundsTransform = { _, _ ->
+ tween(animationDuration, easing = animEasing)
+ },
+ )
+ .background(MaterialTheme.colors.background, RoundedCornerShape(50))
+ .let {
+ if (mode == FabToolbarMode.Toolbar) {
+ // Respect toolbar content size when in Toolbar mode
+ it.wrapContentSize().padding(8.dp)
+ } else {
+ // Resize the container so that it doesn't go beyond the Fab box,
+ // clipping the toolbar as needed
+ it.fillMaxWidth().wrapContentHeight().padding(8.dp)
+ }
+ }
+ ) {
+ // Toolbar - Fixed Size
+ Row(
+ modifier = Modifier.align(Alignment.Center),
+ horizontalArrangement = Arrangement.spacedBy(26.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ val iconSize = DpSize(30.dp, 20.dp)
+ Icon(
+ imageVector = Icons.Outlined.Share,
+ contentDescription = "Share",
+ modifier = Modifier.size(iconSize)
+ )
+ Icon(
+ imageVector = Icons.Outlined.FavoriteBorder,
+ contentDescription = "Favorite",
+ modifier = Modifier.size(iconSize)
+ )
+ Box(modifier = Modifier.size(iconSize)) {
+ // Slot for the Edit Icon when position on the toolbar
+ if (mode == FabToolbarMode.Toolbar) {
+ myEditIcon(Modifier.align(Alignment.Center))
+ }
+ }
+ }
+ }
+ }
+ }
+
+ LookaheadScope {
+ Box(
+ modifier.clickable {
+ mode =
+ if (mode == FabToolbarMode.Fab) {
+ FabToolbarMode.Toolbar
+ } else {
+ FabToolbarMode.Fab
+ }
+ }
+ ) {
+ Box(
+ Modifier.align(Alignment.Center),
+ ) {
+ // Slot 0 - Toolbar position
+ if (mode == FabToolbarMode.Toolbar) {
+ // The Toolbar container should also place the Edit Icon at this state
+ myToolbar(Modifier.align(Alignment.Center))
+ }
+ }
+ Box(Modifier.size(80.dp).align(Alignment.CenterEnd)) {
+ // Slot 1 - Fab position
+ if (mode == FabToolbarMode.Fab) {
+ // We pull out the Edit Icon in this state
+ myToolbar(Modifier.align(Alignment.Center))
+ myEditIcon(Modifier.align(Alignment.Center))
+ }
+ }
+ }
+ }
+}
+
+enum class FabToolbarMode {
+ Fab,
+ Toolbar
+}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadInScrollingColumn.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadInScrollingColumn.kt
new file mode 100644
index 0000000..69173ea
--- /dev/null
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadInScrollingColumn.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2024 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.animation.demos.lookahead
+
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
+import androidx.compose.animation.core.VisibilityThreshold
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.demos.layoutanimation.turquoiseColors
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentWithReceiverOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.LookaheadScope
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+
+/**
+ * A simple example showing how [animateBounds] behaves when animating from/to a scrolling layout.
+ *
+ * Note that despite the items position changing due to the scroll, it does not affect or trigger an
+ * animation.
+ */
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Composable
+@Preview
+fun LookaheadInScrollingColumn() {
+ var displayInScroller by remember { mutableStateOf(false) }
+ val movableContent = remember {
+ movableContentWithReceiverOf<LookaheadScope> {
+ Box(
+ Modifier.zIndex(1f)
+ .let {
+ if (displayInScroller) {
+ it.height(80.dp).fillMaxWidth()
+ } else {
+ it.size(150.dp)
+ }
+ }
+ .animateBounds(
+ lookaheadScope = this@movableContentWithReceiverOf,
+ boundsTransform = { _, _ ->
+ spring(stiffness = 50f, visibilityThreshold = Rect.VisibilityThreshold)
+ }
+ )
+ .clickable { displayInScroller = !displayInScroller }
+ .background(color, RoundedCornerShape(10.dp))
+ )
+ }
+ }
+
+ Box(Modifier.fillMaxSize()) {
+ LookaheadScope {
+ Column(
+ modifier =
+ Modifier.fillMaxSize().verticalScroll(rememberScrollState(0)).padding(10.dp),
+ verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ Text("Click Yellow box to animate to/from scrolling list.")
+ repeat(6) {
+ Box(
+ Modifier.fillMaxWidth()
+ .background(turquoiseColors[it % 6], RoundedCornerShape(10.dp))
+ .height(80.dp)
+ )
+ }
+ if (displayInScroller) {
+ movableContent()
+ }
+ repeat(6) {
+ Box(
+ Modifier.animateBounds(lookaheadScope = this@LookaheadScope)
+ .background(turquoiseColors[it % 6], RoundedCornerShape(10.dp))
+ .height(80.dp)
+ .fillMaxWidth()
+ )
+ }
+ }
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomEnd) {
+ if (!displayInScroller) {
+ movableContent()
+ }
+ }
+ }
+ }
+}
+
+private val color = Color(0xffffcc5c)
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadSamplesDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadSamplesDemo.kt
index f0375d7..8af930f 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadSamplesDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadSamplesDemo.kt
@@ -16,17 +16,56 @@
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
+import androidx.compose.animation.core.ExperimentalAnimatableApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+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.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.samples.LookaheadLayoutCoordinatesSample
-import androidx.compose.ui.samples.approachLayoutSample
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
@Preview
@Composable
fun LookaheadSamplesDemo() {
Column {
- approachLayoutSample()
+ ApproachLayoutSample0()
LookaheadLayoutCoordinatesSample()
}
}
+
+@OptIn(ExperimentalAnimatableApi::class, ExperimentalSharedTransitionApi::class)
+@Composable
+public fun ApproachLayoutSample0() {
+ var fullWidth by remember { mutableStateOf(false) }
+ LookaheadScope {
+ Row(
+ (if (fullWidth) Modifier.fillMaxWidth() else Modifier.width(100.dp))
+ .height(200.dp)
+ // Use the custom modifier created above to animate the constraints passed
+ // to the child, and therefore resize children in an animation.
+ .animateBounds(this@LookaheadScope)
+ .clickable { fullWidth = !fullWidth }
+ ) {
+ Box(
+ Modifier.weight(1f).fillMaxHeight().background(Color(0xffff6f69)),
+ )
+ Box(Modifier.weight(2f).fillMaxHeight().background(Color(0xffffcc5c)))
+ }
+ }
+}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithAnimatedContentSize.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithAnimatedContentSize.kt
index 36f8fec..34c468d 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithAnimatedContentSize.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithAnimatedContentSize.kt
@@ -16,6 +16,8 @@
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.demos.gesture.pastelColors
import androidx.compose.foundation.background
@@ -34,6 +36,7 @@
import androidx.compose.ui.zIndex
import kotlinx.coroutines.delay
+@OptIn(ExperimentalSharedTransitionApi::class)
@Preview
@Composable
fun LookaheadWithAnimatedContentSize() {
@@ -56,7 +59,12 @@
Box(Modifier.fillMaxWidth().height(200.dp).background(Color.White))
}
}
- Box(Modifier.animateBounds().fillMaxWidth().height(100.dp).background(pastelColors[1]))
+ Box(
+ Modifier.animateBounds(this@LookaheadScope)
+ .fillMaxWidth()
+ .height(100.dp)
+ .background(pastelColors[1])
+ )
}
}
}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithBoxWithConstraints.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithBoxWithConstraints.kt
index 24f929f..48ffde5 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithBoxWithConstraints.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithBoxWithConstraints.kt
@@ -16,6 +16,8 @@
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.animation.demos.gesture.pastelColors
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -46,6 +48,7 @@
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.unit.dp
+@OptIn(ExperimentalSharedTransitionApi::class)
@Suppress("UnusedBoxWithConstraintsScope")
@Composable
fun LookaheadWithBoxWithConstraints() {
@@ -59,6 +62,7 @@
Column(
Modifier.fillMaxHeight()
.animateBounds(
+ this@LookaheadScope,
if (halfSize) Modifier.fillMaxSize(0.5f) else Modifier.fillMaxWidth()
)
.background(pastelColors[2]),
@@ -96,7 +100,10 @@
BoxWithConstraints {
Column(
if (animate) {
- Modifier.animateBounds(Modifier.fillMaxWidth())
+ Modifier.animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ Modifier.fillMaxWidth()
+ )
} else {
Modifier.fillMaxWidth()
}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithFlowRowDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithFlowRowDemo.kt
index 6e8296d..d3dd741 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithFlowRowDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithFlowRowDemo.kt
@@ -14,8 +14,12 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalSharedTransitionApi::class)
+
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
@@ -76,17 +80,26 @@
) {
Box(
Modifier.height(50.dp)
- .animateBounds(Modifier.fillMaxWidth(if (isHorizontal) 0.4f else 1f))
+ .animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ Modifier.fillMaxWidth(if (isHorizontal) 0.4f else 1f)
+ )
.background(colors[0], RoundedCornerShape(10))
)
Box(
Modifier.height(50.dp)
- .animateBounds(Modifier.fillMaxWidth(if (isHorizontal) 0.2f else 0.4f))
+ .animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ Modifier.fillMaxWidth(if (isHorizontal) 0.2f else 0.4f)
+ )
.background(colors[1], RoundedCornerShape(10))
)
Box(
Modifier.height(50.dp)
- .animateBounds(Modifier.fillMaxWidth(if (isHorizontal) 0.2f else 0.4f))
+ .animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ Modifier.fillMaxWidth(if (isHorizontal) 0.2f else 0.4f)
+ )
.background(colors[2], RoundedCornerShape(10))
)
}
@@ -136,12 +149,19 @@
var expanded by remember { mutableStateOf(false) }
Box(
modifier =
- Modifier.animateBounds(Modifier.widthIn(max = 600.dp)).background(Color.Red)
+ Modifier.animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ Modifier.widthIn(max = 600.dp)
+ )
+ .background(Color.Red)
) {
val height = animateDpAsState(targetValue = if (expanded) 500.dp else 300.dp)
Box(
modifier =
- Modifier.animateBounds(Modifier.fillMaxWidth().height(height.value))
+ Modifier.animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ Modifier.fillMaxWidth().height(height.value)
+ )
.clickable { expanded = !expanded }
)
}
@@ -155,8 +175,8 @@
modifier =
Modifier.size(200.dp)
.animateBounds(
+ lookaheadScope = this@LookaheadScope,
Modifier.wrapContentWidth().heightIn(min = 156.dp),
- debug = true
)
.background(Color.Blue)
) {
@@ -166,8 +186,8 @@
modifier =
Modifier.size(200.dp)
.animateBounds(
+ lookaheadScope = this@LookaheadScope,
Modifier.wrapContentWidth().heightIn(min = 156.dp),
- debug = true
)
.background(Color.Yellow)
) {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithIntrinsicsDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithIntrinsicsDemo.kt
index c359741..a38e88b 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithIntrinsicsDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithIntrinsicsDemo.kt
@@ -16,6 +16,8 @@
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -43,6 +45,7 @@
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.unit.dp
+@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun LookaheadWithIntrinsicsDemo() {
Column {
@@ -64,6 +67,7 @@
) {
Box(
Modifier.animateBounds(
+ lookaheadScope = this@LookaheadScope,
if (isWide) Modifier.width(300.dp) else Modifier.width(150.dp)
)
.height(50.dp)
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithLazyColumn.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithLazyColumn.kt
index 6f6dc17b..0c0acba 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithLazyColumn.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithLazyColumn.kt
@@ -17,8 +17,11 @@
package androidx.compose.animation.demos.lookahead
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.core.spring
import androidx.compose.animation.demos.R
import androidx.compose.animation.demos.gesture.pastelColors
@@ -47,6 +50,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.LookaheadScope
@@ -54,6 +58,7 @@
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+@OptIn(ExperimentalSharedTransitionApi::class)
@Preview
@Composable
fun LookaheadWithLazyColumn() {
@@ -74,10 +79,7 @@
LookaheadScope {
val title = remember {
movableContentOf {
- Text(
- names[index],
- Modifier.padding(20.dp).animateBounds(Modifier)
- )
+ Text(names[index], Modifier.padding(20.dp).animateBounds(this))
}
}
val image = remember {
@@ -89,9 +91,16 @@
modifier =
Modifier.padding(10.dp)
.animateBounds(
+ this,
if (expanded) Modifier.fillMaxWidth()
else Modifier.size(80.dp),
- spring(stiffness = Spring.StiffnessLow)
+ { _, _ ->
+ spring(
+ Spring.DampingRatioNoBouncy,
+ Spring.StiffnessLow,
+ Rect.VisibilityThreshold
+ )
+ }
)
.clip(RoundedCornerShape(5.dp)),
contentScale =
@@ -108,10 +117,17 @@
modifier =
Modifier.padding(10.dp)
.animateBounds(
+ lookaheadScope = this,
if (expanded)
Modifier.fillMaxWidth().aspectRatio(1f)
else Modifier.size(80.dp),
- spring(stiffness = Spring.StiffnessLow)
+ { _, _ ->
+ spring(
+ Spring.DampingRatioNoBouncy,
+ Spring.StiffnessLow,
+ Rect.VisibilityThreshold
+ )
+ }
)
.background(
Color.LightGray,
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithMovableContentDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithMovableContentDemo.kt
index 2e82042..a02fc9a 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithMovableContentDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithMovableContentDemo.kt
@@ -16,6 +16,8 @@
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.animation.core.DeferredTargetAnimation
import androidx.compose.animation.core.ExperimentalAnimatableApi
import androidx.compose.animation.core.VectorConverter
@@ -58,6 +60,7 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
+@OptIn(ExperimentalSharedTransitionApi::class)
@Preview
@Composable
fun LookaheadWithMovableContentDemo() {
@@ -91,7 +94,7 @@
Modifier.padding(15.dp)
.height(80.dp)
.fillMaxWidth(weight)
- .animateBoundsInScope()
+ .animateBounds(lookaheadScope = this@movableContentWithReceiverOf)
.background(color, RoundedCornerShape(20)),
contentAlignment = Alignment.Center
) {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithPopularBoxWithConstraintsUsage.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithPopularBoxWithConstraintsUsage.kt
index 7181a1b..f38a073 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithPopularBoxWithConstraintsUsage.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithPopularBoxWithConstraintsUsage.kt
@@ -16,6 +16,8 @@
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.demos.R
import androidx.compose.animation.demos.gesture.pastelColors
@@ -50,6 +52,7 @@
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
+@OptIn(ExperimentalSharedTransitionApi::class)
@Preview
@Composable
fun LookaheadWithPopularBoxWithConstraintsUsage() {
@@ -67,7 +70,7 @@
LookaheadScope {
Box(
Modifier.fillMaxSize()
- .animateBounds(Modifier.padding(padding))
+ .animateBounds(this, Modifier.padding(padding))
.background(pastelColors[3])
) {
DetailsContent()
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithScaffold.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithScaffold.kt
index c2ee5bd..08f1c8a 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithScaffold.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithScaffold.kt
@@ -16,6 +16,8 @@
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.TweenSpec
import androidx.compose.foundation.background
@@ -77,6 +79,7 @@
private val colors =
listOf(Color(0xffff6f69), Color(0xffffcc5c), Color(0xff264653), Color(0xff2a9d84))
+@OptIn(ExperimentalSharedTransitionApi::class)
@Preview
@Composable
fun LookaheadWithScaffold() {
@@ -91,7 +94,10 @@
Box(
Modifier.fillMaxHeight()
.background(Color.Gray)
- .animateBounds(if (hasPadding) Modifier.padding(bottom = 300.dp) else Modifier)
+ .animateBounds(
+ this@LookaheadScope,
+ if (hasPadding) Modifier.padding(bottom = 300.dp) else Modifier
+ )
) {
var state by remember { mutableIntStateOf(0) }
val titles =
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithSubcompose.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithSubcompose.kt
index 098f6a2..2ea3bd2 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithSubcompose.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithSubcompose.kt
@@ -16,6 +16,8 @@
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -107,10 +109,11 @@
}
context(LookaheadScope)
+@OptIn(ExperimentalSharedTransitionApi::class)
private fun Modifier.conditionallyAnimateBounds(
shouldAnimate: Boolean,
modifier: Modifier = Modifier
-) = if (shouldAnimate) this.animateBounds(modifier) else this.then(modifier)
+) = if (shouldAnimate) this.animateBounds(this@LookaheadScope, modifier) else this.then(modifier)
private val colors =
listOf(Color(0xffff6f69), Color(0xffffcc5c), Color(0xff2a9d84), Color(0xff264653))
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithTabRowDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithTabRowDemo.kt
index 07df45b..a83f31c 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithTabRowDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithTabRowDemo.kt
@@ -16,6 +16,8 @@
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -50,6 +52,7 @@
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
+@OptIn(ExperimentalSharedTransitionApi::class)
@Preview
@Composable
fun LookaheadWithTabRowDemo() {
@@ -63,7 +66,10 @@
}
Column(
Modifier.fillMaxWidth()
- .animateBounds(if (isWide) Modifier else Modifier.padding(end = 100.dp))
+ .animateBounds(
+ this@LookaheadScope,
+ if (isWide) Modifier else Modifier.padding(end = 100.dp)
+ )
.fillMaxHeight()
.background(Color(0xFFfffbd0))
) {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/SceneHostExperiment.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/SceneHostExperiment.kt
index 1d877f5..1b94fc1 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/SceneHostExperiment.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/SceneHostExperiment.kt
@@ -19,6 +19,7 @@
import androidx.compose.animation.core.AnimationVector2D
import androidx.compose.animation.core.DeferredTargetAnimation
import androidx.compose.animation.core.ExperimentalAnimatableApi
+import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.spring
@@ -36,6 +37,7 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.layout.LookaheadScope
+import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
@@ -43,6 +45,7 @@
import androidx.compose.ui.unit.round
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
+import kotlinx.coroutines.CoroutineScope
@Composable
fun SceneHost(modifier: Modifier = Modifier, content: @Composable SceneScope.() -> Unit) {
@@ -149,3 +152,26 @@
}
}
}
+
+context(LookaheadScope, Placeable.PlacementScope, CoroutineScope)
+@OptIn(ExperimentalAnimatableApi::class)
+internal fun DeferredTargetAnimation<IntOffset, AnimationVector2D>.updateTargetBasedOnCoordinates(
+ animationSpec: FiniteAnimationSpec<IntOffset>,
+): IntOffset {
+ coordinates?.let { coordinates ->
+ with(this@PlacementScope) {
+ val targetOffset = lookaheadScopeCoordinates.localLookaheadPositionOf(coordinates)
+ val animOffset =
+ updateTarget(
+ targetOffset.round(),
+ this@CoroutineScope,
+ animationSpec,
+ )
+ val current =
+ lookaheadScopeCoordinates.localPositionOf(coordinates, Offset.Zero).round()
+ return (animOffset - current)
+ }
+ }
+
+ return IntOffset.Zero
+}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/ScreenSizeChangeDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/ScreenSizeChangeDemo.kt
index 2717e49..497f9b2 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/ScreenSizeChangeDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/ScreenSizeChangeDemo.kt
@@ -16,6 +16,8 @@
package androidx.compose.animation.demos.lookahead
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -137,11 +139,13 @@
}
}
+@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun Root(state: DisplayState) {
SceneHost {
Row(
Modifier.animateBounds(
+ this,
if (state == DisplayState.Compact) {
Modifier.wrapContentSize(align = Alignment.TopStart, unbounded = true)
.requiredWidth(800.dp)
@@ -322,10 +326,12 @@
}
}
+@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun SceneScope.NavRail(state: DisplayState) {
Column(
Modifier.animateBounds(
+ this,
if (state == DisplayState.Tablet) Modifier.width(200.dp)
else Modifier.width(IntrinsicSize.Min)
)
diff --git a/compose/animation/animation/samples/build.gradle b/compose/animation/animation/samples/build.gradle
index a3be858..66c33e58 100644
--- a/compose/animation/animation/samples/build.gradle
+++ b/compose/animation/animation/samples/build.gradle
@@ -36,11 +36,11 @@
compileOnly(project(":annotation:annotation-sampled"))
implementation(project(":compose:animation:animation"))
- implementation("androidx.compose.foundation:foundation:1.2.1")
- implementation("androidx.compose.material:material:1.2.1")
- implementation("androidx.compose.material:material-icons-core:1.6.7")
- implementation("androidx.compose.runtime:runtime:1.2.1")
- implementation("androidx.compose.ui:ui-text:1.2.1")
+ implementation("androidx.compose.foundation:foundation:1.6.8")
+ implementation("androidx.compose.material:material:1.6.8")
+ implementation("androidx.compose.material:material-icons-core:1.6.8")
+ implementation("androidx.compose.runtime:runtime:1.6.8")
+ implementation("androidx.compose.ui:ui-text:1.6.8")
}
androidx {
diff --git a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimateBoundsModifierSample.kt b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimateBoundsModifierSample.kt
new file mode 100644
index 0000000..70dbcfa
--- /dev/null
+++ b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimateBoundsModifierSample.kt
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2024 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.animation.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.animation.BoundsTransform
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.animateBounds
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.VisibilityThreshold
+import androidx.compose.animation.core.keyframesWithSpline
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
+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.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentWithReceiverOf
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.LookaheadScope
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.round
+import androidx.compose.ui.util.fastForEach
+
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Sampled
+@Composable
+private fun AnimateBounds_animateOnContentChange() {
+ // Example where the change in content triggers the layout change on the item with animateBounds
+ val textShort = remember { "Foo ".repeat(10) }
+ val textLong = remember { "Bar ".repeat(50) }
+
+ var toggle by remember { mutableStateOf(true) }
+
+ LookaheadScope {
+ Box(
+ modifier = Modifier.fillMaxSize().clickable { toggle = !toggle },
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = if (toggle) textShort else textLong,
+ modifier =
+ Modifier.fillMaxWidth(0.7f)
+ .background(Color.LightGray)
+ .animateBounds(this@LookaheadScope)
+ .padding(10.dp),
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Sampled
+@Composable
+private fun AnimateBounds_withLayoutModifier() {
+ // Example showing the difference between providing a Layout Modifier as a parameter of
+ // `animateBounds` and chaining the Layout Modifier.
+
+ // We use `padding` in this example, as it provides an immediate change in layout to its child,
+ // but not the parent, which sees the same resulting layout. The difference can be seen in the
+ // Text (content under padding) and an accompanying Cyan Box (a sibling, under the same Row
+ // parent).
+ LookaheadScope {
+ val boundsTransform = remember {
+ BoundsTransform { _, _ ->
+ spring(stiffness = 50f, visibilityThreshold = Rect.VisibilityThreshold)
+ }
+ }
+
+ var toggleAnimation by remember { mutableStateOf(true) }
+
+ Column(Modifier.clickable { toggleAnimation = !toggleAnimation }) {
+ Text(
+ "See the difference in animation when the Layout Modifier is a parameter of animateBounds. Padding, in this example."
+ )
+ Spacer(Modifier.height(12.dp))
+ Text("Layout Modifier as a parameter.")
+ Row(Modifier.fillMaxWidth()) {
+ Box(
+ Modifier.animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ modifier =
+ // By providing this Modifier as a parameter of `animateBounds`,
+ // both content and parent see a gradual/animated change in Layout.
+ Modifier.padding(
+ horizontal = if (toggleAnimation) 10.dp else 50.dp
+ ),
+ boundsTransform = boundsTransform
+ )
+ .background(Color.Red, RoundedCornerShape(12.dp))
+ .height(50.dp)
+ ) {
+ Text("Layout Content", Modifier.align(Alignment.Center))
+ }
+ Box(Modifier.size(50.dp).background(Color.Cyan, RoundedCornerShape(12.dp)))
+ }
+ Spacer(Modifier.height(12.dp))
+ Text("Layout Modifier after AnimateBounds.")
+ Row(Modifier.fillMaxWidth()) {
+ Box(
+ Modifier.animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ boundsTransform = boundsTransform
+ )
+ // The content is able to animate the change in padding, but since the
+ // parent Layout sees no difference, the change in position is immediate.
+ .padding(horizontal = if (toggleAnimation) 10.dp else 50.dp)
+ .background(Color.Red, RoundedCornerShape(12.dp))
+ .height(50.dp)
+ ) {
+ Text("Layout Content", Modifier.align(Alignment.Center))
+ }
+ Box(Modifier.size(50.dp).background(Color.Cyan, RoundedCornerShape(12.dp)))
+ }
+ Spacer(Modifier.height(12.dp))
+ Text("Layout Modifier before AnimateBounds.")
+ Row(Modifier.fillMaxWidth()) {
+ Box(
+ Modifier
+ // The parent is able to see the change in position and the animated size,
+ // so it can smoothly place both its children, but the content of the Box
+ // cannot see the gradual changes so it remains constant.
+ .padding(horizontal = if (toggleAnimation) 10.dp else 50.dp)
+ .animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ boundsTransform = boundsTransform
+ )
+ .background(Color.Red, RoundedCornerShape(12.dp))
+ .height(50.dp)
+ ) {
+ Text("Layout Content", Modifier.align(Alignment.Center))
+ }
+ Box(Modifier.size(50.dp).background(Color.Cyan, RoundedCornerShape(12.dp)))
+ }
+ }
+ }
+}
+
+@OptIn(
+ ExperimentalLayoutApi::class,
+ ExperimentalSharedTransitionApi::class,
+)
+@Sampled
+@Composable
+private fun AnimateBounds_inFlowRowSample() {
+ var itemRowCount by remember { mutableIntStateOf(1) }
+ val colors = remember { listOf(Color.Cyan, Color.Magenta, Color.Yellow, Color.Green) }
+
+ // A case showing `animateBounds` being used to animate layout changes driven by a parent Layout
+ LookaheadScope {
+ Column(Modifier.clickable { itemRowCount = if (itemRowCount != 2) 2 else 1 }) {
+ Text("Click to toggle animation.")
+ FlowRow(
+ modifier =
+ Modifier.fillMaxWidth()
+ // Note that the wrap content size changes for FlowRow as the content
+ // adjusts
+ // to one or two lines, we can simply use `animateContentSize()` to make
+ // sure
+ // all items are visible during their animation.
+ .animateContentSize(),
+ // Try changing the arrangement as well!
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ // We use the maxItems parameter to change the layout of the FlowRow at different
+ // states
+ maxItemsInEachRow = itemRowCount
+ ) {
+ colors.fastForEach {
+ Box(
+ Modifier.animateBounds(this@LookaheadScope)
+ // Note the modifier order, we declare the background after
+ // `animateBounds` to make sure it animates with the rest of the content
+ .background(it, RoundedCornerShape(12.dp))
+ .weight(weight = 1f, fill = true)
+ .height(100.dp)
+ )
+ }
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Sampled
+@Composable
+private fun AnimateBounds_usingKeyframes() {
+ var toggle by remember { mutableStateOf(true) }
+
+ // Example using BoundsTransform to calculate an animation using keyframes with splines.
+ LookaheadScope {
+ Box(Modifier.fillMaxSize().clickable { toggle = !toggle }) {
+ Text(
+ text = "Hello, World!",
+ textAlign = TextAlign.Center,
+ modifier =
+ Modifier.align(if (toggle) Alignment.TopStart else Alignment.TopEnd)
+ .animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ boundsTransform = { initialBounds, targetBounds ->
+ // We'll use a keyframe to emphasize the animation in position and
+ // size.
+ keyframesWithSpline {
+ durationMillis = 1200
+
+ // Emphasize with an increase in size
+ val size = targetBounds.size.times(2f)
+
+ // Emphasize the path with a slight curve at the halfway point
+ val position =
+ targetBounds.topLeft
+ .plus(initialBounds.topLeft)
+ .times(0.5f)
+ .plus(
+ Offset(
+ // Consider the increase in size (from the
+ // center,
+ // to keep the Layout aligned at the keyframe)
+ x = -(size.width - targetBounds.width) * 0.5f,
+ // Emphasize the path with a vertical offset
+ y = size.height * 0.5f
+ )
+ )
+
+ // Only need to define the intermediate keyframe, initial and
+ // target are implicit.
+ Rect(position, size).atFraction(0.5f).using(LinearEasing)
+ }
+ }
+ )
+ .background(Color.LightGray, RoundedCornerShape(50))
+ .padding(10.dp)
+ // Text is laid out with the animated fixed Constraints, relax constraints
+ // back to wrap content to be able to center Align vertically.
+ .wrapContentSize(Alignment.Center)
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Sampled
+@Composable
+private fun AnimateBounds_withMovableContent() {
+ // Example showing how to animate a Layout that can be presented on different Layout Composables
+ // as the state changes using `movableContent`.
+ var position by remember { mutableIntStateOf(-1) }
+
+ val movableContent = remember {
+ // To animate a Layout that can be presented in different Composables, we can use
+ // `animateBounds` with `movableContent`.
+ movableContentWithReceiverOf<LookaheadScope> {
+ Box(
+ Modifier.animateBounds(
+ lookaheadScope = this@movableContentWithReceiverOf,
+ boundsTransform = { _, _ ->
+ spring(
+ dampingRatio = Spring.DampingRatioLowBouncy,
+ stiffness = Spring.StiffnessVeryLow,
+ visibilityThreshold = Rect.VisibilityThreshold
+ )
+ }
+ )
+ // Our movableContent can always fill its container in this example.
+ .fillMaxSize()
+ .background(Color.Cyan, RoundedCornerShape(8.dp))
+ )
+ }
+ }
+
+ LookaheadScope {
+ Box(Modifier.fillMaxSize()) {
+ // Initial container of our Layout, at the center of the screen.
+ Box(
+ Modifier.size(200.dp)
+ .border(3.dp, Color.Red, RoundedCornerShape(8.dp))
+ .align(Alignment.Center)
+ .clickable { position = -1 }
+ ) {
+ if (position < 0) {
+ movableContent()
+ }
+ }
+
+ repeat(4) { index ->
+ // Four additional Boxes where our content may be move to.
+ Box(
+ Modifier.size(100.dp)
+ .border(2.dp, Color.Blue, RoundedCornerShape(8.dp))
+ .align { size, space, _ ->
+ val horizontal = if (index % 2 == 0) 0.15f else 0.85f
+ val vertical = if (index < 2) 0.15f else 0.85f
+
+ Offset(
+ x = (space.width - size.width) * horizontal,
+ y = (space.height - size.height) * vertical
+ )
+ .round()
+ }
+ .clickable { position = index }
+ ) {
+ if (position == index) {
+ // The call to movable content will trigger `Modifier.animateBounds()` to
+ // animate the content's position and size from its previous state.
+ movableContent()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimateBoundsTest.kt b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimateBoundsTest.kt
new file mode 100644
index 0000000..c69c971b
--- /dev/null
+++ b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimateBoundsTest.kt
@@ -0,0 +1,556 @@
+/*
+ * Copyright 2024 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.animation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.keyframes
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentWithReceiverOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.LookaheadScope
+import androidx.compose.ui.layout.boundsInParent
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.round
+import androidx.compose.ui.util.fastRoundToInt
+import androidx.compose.ui.util.lerp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import kotlin.math.roundToInt
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalSharedTransitionApi::class)
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class AnimateBoundsTest {
+
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun animatePosition() =
+ with(rule.density) {
+ val frames = 14 // Even number to reliably test at half duration
+ val durationMillis = frames * 16
+ val rootSizePx = 100
+ val boxSizePX = 20
+
+ var boxPosition = IntOffset.Zero
+
+ var isAtStart by mutableStateOf(true)
+
+ rule.setContent {
+ Box(modifier = Modifier.size(rootSizePx.toDp())) {
+ LookaheadScope {
+ Box(
+ modifier =
+ Modifier.align(
+ if (isAtStart) Alignment.TopStart else Alignment.BottomEnd
+ )
+ .size(boxSizePX.toDp())
+ .animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ boundsTransform = { _, _ ->
+ tween(durationMillis, easing = LinearEasing)
+ }
+ )
+ .drawBehind { drawRect(Color.LightGray) }
+ .onGloballyPositioned {
+ boxPosition = it.positionInParent().round()
+ }
+ )
+ }
+ }
+ }
+ rule.waitForIdle()
+
+ // At TopStart (0, 0)
+ assertEquals(IntOffset.Zero, boxPosition)
+
+ // AutoAdvance off to test animation at different points
+ rule.mainClock.autoAdvance = false
+ isAtStart = false
+ rule.waitForIdle()
+ rule.mainClock.advanceTimeByFrame()
+ rule.mainClock.advanceTimeByFrame()
+
+ // Advance to the middle of the animation
+ rule.mainClock.advanceTimeBy(durationMillis / 2L)
+
+ val expectedPosPx = (rootSizePx - boxSizePX) * 0.5f
+ val expectedIntOffset = Offset(expectedPosPx, expectedPosPx).round()
+ assertEquals(expectedIntOffset, boxPosition)
+
+ // AutoAdvance ON to finish the animation
+ rule.mainClock.autoAdvance = true
+ rule.waitForIdle()
+
+ // At BottomEnd (parentSize - boxSize, parentSize - boxSize)
+ val expectedFinalPos = rootSizePx - boxSizePX
+ assertEquals(IntOffset(expectedFinalPos, expectedFinalPos), boxPosition)
+ }
+
+ @Test
+ fun animateSize() =
+ with(rule.density) {
+ val frameTime = 16 // milliseconds
+ val frames = 14 // Even number to reliable test at half duration
+ val durationMillis = frames * frameTime
+ val rootSizePx = 400
+ val boxSizeSmallPx = rootSizePx * 0.25f
+ val boxSizeLargePx = rootSizePx * 0.5f
+
+ val expectedLargeSize = Size(boxSizeLargePx, boxSizeLargePx)
+ val expectedSmallSize = Size(boxSizeSmallPx, boxSizeSmallPx)
+
+ var boxSize = IntSize.Zero
+
+ var isExpanded by mutableStateOf(false)
+
+ rule.setContent {
+ Box(Modifier.size(rootSizePx.toDp())) {
+ LookaheadScope {
+ Box(
+ Modifier.size(
+ if (isExpanded) boxSizeLargePx.toDp() else boxSizeSmallPx.toDp()
+ )
+ .animateBounds(
+ lookaheadScope = this,
+ boundsTransform = { _, _ ->
+ tween(
+ durationMillis = durationMillis,
+ easing = LinearEasing
+ )
+ }
+ )
+ .drawBehind { drawRect(Color.LightGray) }
+ .onGloballyPositioned { boxSize = it.size }
+ )
+ }
+ }
+ }
+ rule.waitForIdle()
+ assertEquals(expectedSmallSize.round(), boxSize)
+
+ // AutoAdvance off to test animation at different points
+ rule.mainClock.autoAdvance = false
+ isExpanded = true
+ rule.waitForIdle()
+
+ // Wait until first animated frame, for test stability
+ do {
+ rule.mainClock.advanceTimeByFrame()
+ } while (expectedSmallSize.round() == boxSize)
+
+ // Advance to approx. the middle of the animation (minus the first animated frame)
+ rule.mainClock.advanceTimeBy(durationMillis / 2L - frameTime)
+
+ val expectedMidIntSize = (expectedLargeSize + expectedSmallSize).times(0.5f).round()
+ assertEquals(expectedMidIntSize, boxSize)
+
+ // AutoAdvance ON to finish the animation
+ rule.mainClock.autoAdvance = true
+ rule.waitForIdle()
+
+ assertEquals(expectedLargeSize.round(), boxSize)
+ }
+
+ @Test
+ fun animateBounds() =
+ with(rule.density) {
+ val frames = 14 // Even number to reliable test at half duration
+ val durationMillis = frames * 16
+ val rootSizePx = 400
+ val boxSizeSmallPx = rootSizePx * 0.25f
+ val boxSizeLargePx = rootSizePx * 0.5f
+
+ val expectedLargeSize = Size(boxSizeLargePx, boxSizeLargePx)
+ val expectedSmallSize = Size(boxSizeSmallPx, boxSizeSmallPx)
+ val expectedFinalPos = rootSizePx - boxSizeLargePx
+
+ var boxBounds = Rect(Offset.Zero, Size.Zero)
+
+ var toggle by mutableStateOf(false)
+
+ rule.setContent {
+ Box(Modifier.size(rootSizePx.toDp())) {
+ LookaheadScope {
+ Box(
+ Modifier.then(
+ if (toggle) {
+ Modifier.align(Alignment.BottomEnd)
+ .size(boxSizeLargePx.toDp())
+ } else {
+ Modifier.align(Alignment.TopStart)
+ .size(boxSizeSmallPx.toDp())
+ }
+ )
+ .animateBounds(
+ lookaheadScope = this,
+ boundsTransform = { _, _ ->
+ tween(
+ durationMillis = durationMillis,
+ easing = LinearEasing
+ )
+ }
+ )
+ .drawBehind { drawRect(Color.Yellow) }
+ .onGloballyPositioned { boxBounds = it.boundsInParent() }
+ )
+ }
+ }
+ }
+ rule.waitForIdle()
+ assertEquals(Rect(Offset.Zero, expectedSmallSize), boxBounds)
+
+ // AutoAdvance off to test animation at different points
+ rule.mainClock.autoAdvance = false
+ toggle = true
+ rule.waitForIdle()
+ rule.mainClock.advanceTimeByFrame()
+ rule.mainClock.advanceTimeByFrame()
+
+ // Advance to the middle of the animation
+ rule.mainClock.advanceTimeBy(durationMillis / 2L)
+ rule.waitForIdle()
+
+ // Calculate expected bounds
+ val expectedMidSize = (expectedLargeSize + expectedSmallSize).times(0.5f)
+ val expectedMidPosition = (rootSizePx - boxSizeLargePx) * 0.5f
+ val expectedMidOffset = Offset(expectedMidPosition, expectedMidPosition)
+ val expectedMidBounds = Rect(expectedMidOffset, expectedMidSize)
+
+ assertEquals(expectedMidBounds, boxBounds)
+
+ // AutoAdvance ON to finish the animation
+ rule.mainClock.autoAdvance = true
+ rule.waitForIdle()
+
+ assertEquals(
+ Rect(Offset(expectedFinalPos, expectedFinalPos), expectedLargeSize),
+ boxBounds
+ )
+ }
+
+ @Test
+ fun animateBounds_withIntermediateModifier() =
+ with(rule.density) {
+ val durationMillis = 10 * 16
+
+ var toggleAnimation by mutableStateOf(true)
+
+ val rootWidthPx = 100
+ val padding1Px = 10
+ val padding2Px = 20
+
+ var positionA = IntOffset(-1, -1)
+ var positionB = IntOffset(-1, 1)
+
+ // Change the padding on state change to trigger the animation
+ fun Modifier.applyPadding(): Modifier =
+ this.padding(
+ horizontal =
+ if (toggleAnimation) {
+ padding1Px.toDp()
+ } else {
+ padding2Px.toDp()
+ }
+ )
+
+ rule.setContent {
+ // Based on sample `AnimateBounds_withLayoutModifier`
+ LookaheadScope {
+ Column(Modifier.width(rootWidthPx.toDp())) {
+ Row(Modifier.fillMaxWidth()) {
+ Box(
+ Modifier.animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ modifier = Modifier.applyPadding(),
+ boundsTransform = { _, _,
+ ->
+ tween(durationMillis, easing = LinearEasing)
+ }
+ )
+ ) {
+ Box(
+ Modifier.onGloballyPositioned {
+ positionA = it.positionInRoot().round()
+ }
+ )
+ }
+ }
+ Row(Modifier.fillMaxWidth()) {
+ Box(
+ Modifier.animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ boundsTransform = { _, _,
+ ->
+ tween(durationMillis, easing = LinearEasing)
+ }
+ )
+ .applyPadding()
+ ) {
+ Box(
+ Modifier.onGloballyPositioned {
+ positionB = it.positionInRoot().round()
+ }
+ )
+ }
+ }
+ }
+ }
+ }
+ rule.waitForIdle()
+
+ assertEquals(positionA, IntOffset(padding1Px, 0))
+ assertEquals(positionB, IntOffset(padding1Px, 0))
+
+ rule.mainClock.autoAdvance = false
+ toggleAnimation = !toggleAnimation
+ rule.mainClock.advanceTimeByFrame()
+ rule.mainClock.advanceTimeByFrame()
+
+ // We measure at the first animated frame
+ rule.mainClock.advanceTimeByFrame()
+
+ // Box A has a continuous change in value from having the Modifier as a parameter
+ val expectedPosA =
+ lerp(padding1Px.toFloat(), padding2Px.toFloat(), 16f / durationMillis)
+ assertEquals(positionA, IntOffset(expectedPosA.fastRoundToInt(), 0))
+ // Box B has an immediate change in value from chaining the Modifier
+ assertEquals(positionB, IntOffset(padding2Px, 0))
+ }
+
+ @Test
+ fun animateBounds_usingMovableContent() =
+ with(rule.density) {
+ val frames = 14 // Even number to reliable test at half duration
+ val durationMillis = frames * 16
+
+ val itemASizePx = 30
+ val itemAOffset = IntOffset(70, 70)
+
+ val itemBSizePx = 50
+ val itemBOffset = IntOffset(110, 110)
+
+ var isBoxAtSlotA by mutableStateOf(true)
+
+ var boxPosition = IntOffset.Zero
+ var boxSize = IntSize.Zero
+
+ rule.setContent {
+ val movableBox = remember {
+ movableContentWithReceiverOf<LookaheadScope> {
+ Box(
+ modifier =
+ Modifier.fillMaxSize()
+ .animateBounds(
+ lookaheadScope = this,
+ boundsTransform = { _, _ ->
+ tween(
+ durationMillis = durationMillis,
+ easing = LinearEasing
+ )
+ }
+ )
+ .onGloballyPositioned {
+ boxPosition = it.positionInRoot().round()
+ boxSize = it.size
+ }
+ )
+ }
+ }
+
+ LookaheadScope {
+ Box {
+ Box(Modifier.offset { itemAOffset }.size(itemASizePx.toDp())) {
+ // Slot A
+ if (isBoxAtSlotA) {
+ movableBox()
+ }
+ }
+ Box(Modifier.offset { itemBOffset }.size(itemBSizePx.toDp())) {
+ // Slot B
+ if (!isBoxAtSlotA) {
+ movableBox()
+ }
+ }
+ }
+ }
+ }
+ rule.waitForIdle()
+
+ // Initial conditions
+ assertEquals(itemAOffset, boxPosition)
+ assertEquals(IntSize(itemASizePx, itemASizePx), boxSize)
+
+ // AutoAdvance off to test animation at different points
+ rule.mainClock.autoAdvance = false
+ isBoxAtSlotA = false
+ rule.waitForIdle()
+ rule.mainClock.advanceTimeByFrame()
+ rule.mainClock.advanceTimeByFrame()
+
+ // Advance to the middle of the animation
+ rule.mainClock.advanceTimeBy(durationMillis / 2L)
+ rule.waitForIdle()
+
+ // Evaluate with expected values at half the animation
+ val sizeAtHalfDuration = (itemASizePx + itemBSizePx) / 2
+ assertEquals((itemAOffset + itemBOffset).div(2f), boxPosition)
+ assertEquals(IntSize(sizeAtHalfDuration, sizeAtHalfDuration), boxSize)
+
+ // AutoAdvance ON to finish the animation
+ rule.mainClock.autoAdvance = true
+ rule.waitForIdle()
+
+ assertEquals(itemBOffset, boxPosition)
+ assertEquals(IntSize(itemBSizePx, itemBSizePx), boxSize)
+ }
+
+ @Test
+ fun animateBounds_scrollBehavior() =
+ with(rule.density) {
+ val itemSizePx = 30f
+ val keyFrameOffset = itemSizePx * 5
+
+ var isAnimateScroll by mutableStateOf(false)
+ val scrollState = ScrollState(0)
+
+ var item0Position = IntOffset(-1, -1)
+
+ rule.setContent {
+ LookaheadScope {
+ Column(Modifier.size(itemSizePx.toDp()).verticalScroll(scrollState)) {
+ repeat(2) { index ->
+ Box(
+ modifier =
+ Modifier.size(itemSizePx.toDp())
+ .animateBounds(
+ lookaheadScope = this@LookaheadScope,
+ boundsTransform = { initial, _ ->
+ // Drive the start position to a specific value, by
+ // default
+ // the animation should not happen, and so we should
+ // never
+ // be able to read that value.
+ keyframes {
+ Rect(Offset(0f, keyFrameOffset), initial.size)
+ .at(0)
+ .using(LinearEasing)
+ }
+ },
+ animateMotionFrameOfReference = isAnimateScroll
+ )
+ .onGloballyPositioned {
+ if (index == 0) {
+ item0Position = it.positionInRoot().round()
+ }
+ }
+ )
+ }
+ }
+ }
+ }
+ // First test without animating scroll, note that we still handle the clock, as to allow
+ // any animation to play after we change the scroll.
+ rule.waitForIdle()
+ rule.mainClock.autoAdvance = false
+
+ runBlocking { scrollState.scrollBy(itemSizePx) }
+ rule.waitForIdle()
+
+ // Let animations play for the first frame
+ rule.mainClock.advanceTimeByFrame()
+ rule.mainClock.advanceTimeByFrame()
+
+ // Expected position should immediately reflect scroll changes since we are not
+ // animating it
+ assertEquals(IntOffset(0, -itemSizePx.fastRoundToInt()), item0Position)
+
+ // Finish any pending animations
+ rule.mainClock.autoAdvance = true
+ rule.waitForIdle()
+
+ // Enable scroll animation
+ isAnimateScroll = true
+ rule.waitForIdle()
+ rule.mainClock.autoAdvance = false
+
+ runBlocking {
+ // Scroll back into starting position
+ scrollState.scrollBy(-itemSizePx)
+ }
+ rule.waitForIdle()
+
+ rule.mainClock.advanceTimeByFrame()
+
+ // Position should correspond to the exaggerated keyframe offset.
+ // Note that the keyframe is actually defined around the item's center
+ assertEquals(
+ Offset(
+ // Center position at x = 0
+ x = 0f,
+ // keyframeOffset - (previousScrollOffset) + itemCenterY
+ y = keyFrameOffset
+ )
+ .round(),
+ item0Position
+ )
+ }
+
+ private fun Size.round(): IntSize = IntSize(width.roundToInt(), height.roundToInt())
+
+ private operator fun Size.plus(other: Size) = Size(width + other.width, height + other.height)
+
+ private operator fun Size.minus(other: Size) = Size(width - other.width, height - other.height)
+
+ private operator fun IntSize.minus(other: IntSize) =
+ IntSize(width - other.width, height - other.height)
+
+ private operator fun Rect.minus(other: Rect) =
+ Rect(offset = this.topLeft - other.topLeft, size = this.size - other.size)
+}
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimateBoundsModifier.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimateBoundsModifier.kt
new file mode 100644
index 0000000..388b244
--- /dev/null
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimateBoundsModifier.kt
@@ -0,0 +1,428 @@
+/*
+ * Copyright 2024 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.animation
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector4D
+import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.VisibilityThreshold
+import androidx.compose.animation.core.spring
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.geometry.isUnspecified
+import androidx.compose.ui.layout.ApproachLayoutModifierNode
+import androidx.compose.ui.layout.ApproachMeasureScope
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.LookaheadScope
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.round
+import androidx.compose.ui.unit.roundToIntSize
+import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.util.fastRoundToInt
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.launch
+
+/**
+ * [Modifier] to animate layout changes (position and/or size) that occur within a [LookaheadScope].
+ *
+ * So, the given [lookaheadScope] defines the coordinate space considered to trigger an animation.
+ * For example, if [lookaheadScope] was defined at the root of the app hierarchy, then any layout
+ * changes visible within the screen will trigger an animation, if it, in contrast was defined
+ * within a scrolling parent, then, as long the [LookaheadScope] scrolls with is content, no
+ * animation will be triggered, as there will be no changes within its coordinate space.
+ *
+ * The animation is driven with a [FiniteAnimationSpec] produced by the given [BoundsTransform]
+ * function, which you may use to customize the animations based on the initial and target bounds.
+ *
+ * Do note that certain Layout Modifiers when chained with [animateBounds], may only cause an
+ * immediate observable change to either the child or the parent Layout which can result in
+ * undesired behavior. For those cases you can instead provide it to the [modifier] parameter. This
+ * allows [animateBounds] to envelop the size and constraints change and propagate them gradually to
+ * both its parent and child Layout.
+ *
+ * You may see the difference when supplying a Layout Modifier in [modifier] on the following
+ * example:
+ *
+ * @sample androidx.compose.animation.samples.AnimateBounds_withLayoutModifier
+ *
+ * By default, changes in position under [LayoutCoordinates.introducesMotionFrameOfReference] are
+ * excluded from the animation and are instead immediately applied, as they are expected to be
+ * frequent/continuous (to handle Layouts under Scroll). You may change this behavior by passing
+ * [animateMotionFrameOfReference] as `true`. Keep in mind, doing that under a scroll may result in
+ * the Layout "chasing" the scroll offset, as it will constantly animate to the latest position.
+ *
+ * A basic use-case is animating a layout based on content changes, such as the String changing on a
+ * Text:
+ *
+ * @sample androidx.compose.animation.samples.AnimateBounds_animateOnContentChange
+ *
+ * It also provides an easy way to animate layout changes of a complex Composable Layout:
+ *
+ * @sample androidx.compose.animation.samples.AnimateBounds_inFlowRowSample
+ *
+ * Since [BoundsTransform] is called when initiating an animation, you may also use it to calculate
+ * a keyframe based animation:
+ *
+ * @sample androidx.compose.animation.samples.AnimateBounds_usingKeyframes
+ *
+ * It may also be used together with [movableContent][androidx.compose.runtime.movableContentOf] as
+ * long as the given [LookaheadScope] is in a common place within the Layout hierarchy of the slots
+ * presenting the `movableContent`:
+ *
+ * @sample androidx.compose.animation.samples.AnimateBounds_withMovableContent
+ * @param lookaheadScope The scope from which this [animateBounds] will calculate its animations
+ * from. This implies that as long as you're expecting an animation the reference of the given
+ * [LookaheadScope] shouldn't change, otherwise you may get unexpected behavior.
+ * @param modifier Optional intermediate Modifier, may be used in cases where otherwise immediate
+ * layout changes are perceived as gradual by both the parent and child Layout.
+ * @param boundsTransform Produce a customized [FiniteAnimationSpec] based on the initial and target
+ * bounds, called when an animation is triggered.
+ * @param animateMotionFrameOfReference When `true`, changes under
+ * [LayoutCoordinates.introducesMotionFrameOfReference] (for continuous positional changes, such
+ * as Scroll Offset) are included when calculating an animation. `false` by default, where the
+ * changes are instead applied directly into the layout without triggering an animation.
+ * @see ApproachLayoutModifierNode
+ * @see LookaheadScope
+ */
+@ExperimentalSharedTransitionApi // Depends on BoundsTransform
+public fun Modifier.animateBounds(
+ lookaheadScope: LookaheadScope,
+ modifier: Modifier = Modifier,
+ boundsTransform: BoundsTransform = DefaultBoundsTransform,
+ animateMotionFrameOfReference: Boolean = false,
+): Modifier =
+ this.then(
+ BoundsAnimationElement(
+ lookaheadScope = lookaheadScope,
+ boundsTransform = boundsTransform,
+ // Measure with original constraints.
+ // The layout of this element will still be the animated lookahead size.
+ resolveMeasureConstraints = { _, constraints -> constraints },
+ animateMotionFrameOfReference = animateMotionFrameOfReference,
+ )
+ )
+ .then(modifier)
+ .then(
+ BoundsAnimationElement(
+ lookaheadScope = lookaheadScope,
+ boundsTransform = boundsTransform,
+ resolveMeasureConstraints = { animatedSize, _ ->
+ // For the target Layout, pass the animated size as Constraints.
+ Constraints.fixed(animatedSize.width, animatedSize.height)
+ },
+ animateMotionFrameOfReference = animateMotionFrameOfReference,
+ )
+ )
+
+@ExperimentalSharedTransitionApi
+internal data class BoundsAnimationElement(
+ val lookaheadScope: LookaheadScope,
+ val boundsTransform: BoundsTransform,
+ val resolveMeasureConstraints: (animatedSize: IntSize, constraints: Constraints) -> Constraints,
+ val animateMotionFrameOfReference: Boolean,
+) : ModifierNodeElement<BoundsAnimationModifierNode>() {
+ override fun create(): BoundsAnimationModifierNode {
+ return BoundsAnimationModifierNode(
+ lookaheadScope = lookaheadScope,
+ boundsTransform = boundsTransform,
+ onChooseMeasureConstraints = resolveMeasureConstraints,
+ animateMotionFrameOfReference = animateMotionFrameOfReference,
+ )
+ }
+
+ override fun update(node: BoundsAnimationModifierNode) {
+ node.lookaheadScope = lookaheadScope
+ node.boundsTransform = boundsTransform
+ node.onChooseMeasureConstraints = resolveMeasureConstraints
+ node.animateMotionFrameOfReference = animateMotionFrameOfReference
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "boundsAnimation"
+ properties["lookaheadScope"] = lookaheadScope
+ properties["boundsTransform"] = boundsTransform
+ properties["onChooseMeasureConstraints"] = resolveMeasureConstraints
+ properties["animateMotionFrameOfReference"] = animateMotionFrameOfReference
+ }
+}
+
+/**
+ * [Modifier.Node] implementation that handles the bounds animation with
+ * [ApproachLayoutModifierNode].
+ *
+ * @param lookaheadScope The [LookaheadScope] to animate from.
+ * @param boundsTransform Callback to produce [FiniteAnimationSpec] at every triggered animation
+ * @param onChooseMeasureConstraints Callback to decide whether to measure the Modifier Layout with
+ * the current animated size value or the incoming constraints. This reflects on the
+ * [MeasureResult] of this Modifier Layout as well.
+ * @param animateMotionFrameOfReference Whether to include changes under
+ * [LayoutCoordinates.introducesMotionFrameOfReference] to trigger animations.
+ */
+@ExperimentalSharedTransitionApi
+internal class BoundsAnimationModifierNode(
+ var lookaheadScope: LookaheadScope,
+ var boundsTransform: BoundsTransform,
+ var onChooseMeasureConstraints:
+ (animatedSize: IntSize, constraints: Constraints) -> Constraints,
+ var animateMotionFrameOfReference: Boolean,
+) : ApproachLayoutModifierNode, Modifier.Node() {
+ private val boundsAnimation = BoundsTransformDeferredAnimation()
+
+ override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
+ // Update target size, it will serve to know if we expect an approach in progress
+ boundsAnimation.updateTargetSize(lookaheadSize.toSize())
+
+ return !boundsAnimation.isIdle
+ }
+
+ override fun Placeable.PlacementScope.isPlacementApproachInProgress(
+ lookaheadCoordinates: LayoutCoordinates
+ ): Boolean {
+ // Once we can capture size and offset we may also start the animation
+ boundsAnimation.updateTargetOffsetAndAnimate(
+ lookaheadScope = lookaheadScope,
+ placementScope = this,
+ coroutineScope = coroutineScope,
+ includeMotionFrameOfReference = animateMotionFrameOfReference,
+ boundsTransform = boundsTransform,
+ )
+ return !boundsAnimation.isIdle
+ }
+
+ override fun ApproachMeasureScope.approachMeasure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ // The animated value is null on the first frame as we don't get the full bounds
+ // information until placement, so we can safely use the current Size.
+ val fallbackSize =
+ if (boundsAnimation.currentSize.isUnspecified) {
+ // When using Intrinsics, we may get measured before getting the approach check
+ lookaheadSize.toSize()
+ } else {
+ boundsAnimation.currentSize
+ }
+ val animatedSize = (boundsAnimation.value?.size ?: fallbackSize).roundToIntSize()
+
+ val chosenConstraints = onChooseMeasureConstraints(animatedSize, constraints)
+
+ val placeable = measurable.measure(chosenConstraints)
+ return layout(animatedSize.width, animatedSize.height) {
+ val animatedBounds = boundsAnimation.value
+ val positionInScope =
+ with(lookaheadScope) {
+ coordinates?.let { coordinates ->
+ lookaheadScopeCoordinates.localPositionOf(
+ sourceCoordinates = coordinates,
+ relativeToSource = Offset.Zero,
+ includeMotionFrameOfReference = animateMotionFrameOfReference
+ )
+ }
+ }
+
+ val topLeft =
+ if (animatedBounds != null) {
+ boundsAnimation.updateCurrentBounds(animatedBounds.topLeft, animatedBounds.size)
+ animatedBounds.topLeft
+ } else {
+ boundsAnimation.currentBounds?.topLeft ?: Offset.Zero
+ }
+ val (x, y) = positionInScope?.let { topLeft - it } ?: Offset.Zero
+ placeable.place(x.fastRoundToInt(), y.fastRoundToInt())
+ }
+ }
+}
+
+/** Helper class to keep track of the BoundsAnimation state for [ApproachLayoutModifierNode]. */
+@OptIn(ExperimentalSharedTransitionApi::class)
+internal class BoundsTransformDeferredAnimation {
+ private var animatable: Animatable<Rect, AnimationVector4D>? = null
+
+ private var targetSize: Size = Size.Unspecified
+ private var targetOffset: Offset = Offset.Unspecified
+
+ private var isPending = false
+
+ /**
+ * Captures lookahead size, updates current size for the first pass and marks the animation as
+ * pending.
+ */
+ fun updateTargetSize(size: Size) {
+ if (targetSize.isSpecified && size.roundToIntSize() != targetSize.roundToIntSize()) {
+ // Change in target, animation is pending
+ isPending = true
+ }
+ targetSize = size
+
+ if (currentSize.isUnspecified) {
+ currentSize = size
+ }
+ }
+
+ /**
+ * Captures lookahead position, updates current position for the first pass and marks the
+ * animation as pending.
+ */
+ private fun updateTargetOffset(offset: Offset) {
+ if (targetOffset.isSpecified && offset.round() != targetOffset.round()) {
+ isPending = true
+ }
+ targetOffset = offset
+
+ if (currentPosition.isUnspecified) {
+ currentPosition = offset
+ }
+ }
+
+ // We capture the current bounds parameters individually to avoid unnecessary Rect allocations
+ private var currentPosition: Offset = Offset.Unspecified
+ var currentSize: Size = Size.Unspecified
+
+ val currentBounds: Rect?
+ get() {
+ val size = currentSize
+ val position = currentPosition
+ return if (position.isSpecified && size.isSpecified) {
+ Rect(position, size)
+ } else {
+ null
+ }
+ }
+
+ fun updateCurrentBounds(position: Offset, size: Size) {
+ currentPosition = position
+ currentSize = size
+ }
+
+ val isIdle: Boolean
+ get() = !isPending && animatable?.isRunning != true
+
+ private var animatedValue: Rect? by mutableStateOf(null)
+
+ val value: Rect?
+ get() = if (isIdle) null else animatedValue
+
+ private var directManipulationParents: MutableList<LayoutCoordinates>? = null
+ private var additionalOffset: Offset = Offset.Zero
+
+ fun updateTargetOffsetAndAnimate(
+ lookaheadScope: LookaheadScope,
+ placementScope: Placeable.PlacementScope,
+ coroutineScope: CoroutineScope,
+ includeMotionFrameOfReference: Boolean,
+ boundsTransform: BoundsTransform,
+ ) {
+ placementScope.coordinates?.let { coordinates ->
+ with(lookaheadScope) {
+ val lookaheadScopeCoordinates = placementScope.lookaheadScopeCoordinates
+
+ var delta = Offset.Zero
+ if (!includeMotionFrameOfReference) {
+ // As the Layout changes, we need to keep track of the accumulated offset up
+ // the hierarchy tree, to get the proper Offset accounting for scrolling.
+ val parents = directManipulationParents ?: mutableListOf()
+ var currentCoords = coordinates
+ var index = 0
+
+ // Find the given lookahead coordinates by traversing up the tree
+ while (currentCoords.toLookaheadCoordinates() != lookaheadScopeCoordinates) {
+ if (currentCoords.introducesMotionFrameOfReference) {
+ if (parents.size == index) {
+ parents.add(currentCoords)
+ delta += currentCoords.positionInParent()
+ } else if (parents[index] != currentCoords) {
+ delta -= parents[index].positionInParent()
+ parents[index] = currentCoords
+ delta += currentCoords.positionInParent()
+ }
+ index++
+ }
+ currentCoords = currentCoords.parentCoordinates ?: break
+ }
+
+ for (i in parents.size - 1 downTo index) {
+ delta -= parents[i].positionInParent()
+ parents.removeAt(parents.size - 1)
+ }
+ directManipulationParents = parents
+ }
+ additionalOffset += delta
+
+ val targetOffset =
+ lookaheadScopeCoordinates.localLookaheadPositionOf(
+ sourceCoordinates = coordinates,
+ includeMotionFrameOfReference = includeMotionFrameOfReference
+ )
+ updateTargetOffset(targetOffset + additionalOffset)
+
+ animatedValue =
+ animate(coroutineScope = coroutineScope, boundsTransform = boundsTransform)
+ .translate(-(additionalOffset))
+ }
+ }
+ }
+
+ private fun animate(
+ coroutineScope: CoroutineScope,
+ boundsTransform: BoundsTransform,
+ ): Rect {
+ if (targetOffset.isSpecified && targetSize.isSpecified) {
+ // Initialize Animatable when possible, we might not use it but we need to have it
+ // instantiated since at the first pass the lookahead information will become the
+ // initial bounds when we actually need an animation.
+ val target = Rect(targetOffset, targetSize)
+ val anim = animatable ?: Animatable(target, Rect.VectorConverter)
+ animatable = anim
+
+ // This check should avoid triggering an animation on the first pass, as there would not
+ // be enough information to have a distinct current and target bounds.
+ if (isPending) {
+ isPending = false
+ coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
+ // Dispatch right away to make sure approach callbacks are accurate on `isIdle`
+ anim.animateTo(target, boundsTransform.transform(currentBounds!!, target))
+ }
+ }
+ }
+ return animatable?.value ?: Rect.Zero
+ }
+}
+
+@OptIn(ExperimentalSharedTransitionApi::class)
+private val DefaultBoundsTransform = BoundsTransform { _, _ ->
+ spring(
+ dampingRatio = Spring.DampingRatioNoBouncy,
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = Rect.VisibilityThreshold
+ )
+}
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 88a3de5..bced9b3 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -410,10 +410,10 @@
method @Deprecated public static <T> androidx.compose.foundation.gestures.AnchoredDraggableState<T> AnchoredDraggableState(T initialValue, androidx.compose.foundation.gestures.DraggableAnchors<T> anchors, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
method @Deprecated public static <T> androidx.compose.foundation.gestures.AnchoredDraggableState<T> AnchoredDraggableState(T initialValue, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
method public static <T> androidx.compose.foundation.gestures.DraggableAnchors<T> DraggableAnchors(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.gestures.DraggableAnchorsConfig<T>,kotlin.Unit> builder);
- method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
- method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
- method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
- method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+ method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+ method @Deprecated public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+ method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+ method @Deprecated public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
method public static suspend <T> Object? animateTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public static suspend <T> Object? animateToWithDecay(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, float velocity, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, kotlin.coroutines.Continuation<? super java.lang.Float>);
method public static inline <T> void forEach(androidx.compose.foundation.gestures.DraggableAnchors<T>, kotlin.jvm.functions.Function2<? super T,? super java.lang.Float,kotlin.Unit> block);
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index c0be53e..38305f9 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -412,10 +412,10 @@
method @Deprecated public static <T> androidx.compose.foundation.gestures.AnchoredDraggableState<T> AnchoredDraggableState(T initialValue, androidx.compose.foundation.gestures.DraggableAnchors<T> anchors, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
method @Deprecated public static <T> androidx.compose.foundation.gestures.AnchoredDraggableState<T> AnchoredDraggableState(T initialValue, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
method public static <T> androidx.compose.foundation.gestures.DraggableAnchors<T> DraggableAnchors(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.gestures.DraggableAnchorsConfig<T>,kotlin.Unit> builder);
- method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
- method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
- method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
- method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+ method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+ method @Deprecated public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+ method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+ method @Deprecated public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
method public static suspend <T> Object? animateTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public static suspend <T> Object? animateToWithDecay(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, float velocity, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, kotlin.coroutines.Continuation<? super java.lang.Float>);
method public static inline <T> void forEach(androidx.compose.foundation.gestures.DraggableAnchors<T>, kotlin.jvm.functions.Function2<? super T,? super java.lang.Float,kotlin.Unit> block);
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt
index ddc8782..d9f1be0 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt
@@ -21,7 +21,6 @@
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.samples.AnchoredDraggableAnchorsFromCompositionSample
-import androidx.compose.foundation.samples.AnchoredDraggableCatchAnimatingWidgetSample
import androidx.compose.foundation.samples.AnchoredDraggableCustomAnchoredSample
import androidx.compose.foundation.samples.AnchoredDraggableLayoutDependentAnchorsSample
import androidx.compose.foundation.samples.AnchoredDraggableProgressSample
@@ -40,7 +39,6 @@
Spacer(Modifier.height(50.dp))
AnchoredDraggableLayoutDependentAnchorsSample()
Spacer(Modifier.height(50.dp))
- AnchoredDraggableCatchAnimatingWidgetSample()
Spacer(Modifier.height(50.dp))
AnchoredDraggableCustomAnchoredSample()
Spacer(Modifier.height(50.dp))
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt
index 50dcbe8..84a7799 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt
@@ -314,4 +314,33 @@
rule.onNodeWithTag(LazyStaggeredGrid).assertMainAxisSizeIsEqualTo(itemSizeDp * 6)
}
+
+ @Test
+ fun afterContentPaddingWithSmallScrolls() {
+ state = LazyStaggeredGridState(initialFirstVisibleItemIndex = 0)
+ rule.setContent {
+ Box(Modifier.axisSize(itemSizeDp * 2, itemSizeDp * 4)) {
+ LazyStaggeredGrid(
+ lanes = 2,
+ modifier = Modifier.testTag(LazyStaggeredGrid),
+ contentPadding = PaddingValues(afterContent = itemSizeDp / 2),
+ state = state
+ ) {
+ items(20, key = { it }) {
+ val size = if (it == 0 || it == 19) itemSizeDp / 2 else itemSizeDp * 2
+ Spacer(Modifier.mainAxisSize(size).testTag("$it").debugBorder())
+ }
+ }
+ }
+ }
+
+ // scroll to the end
+ state.scrollBy(itemSizeDp * 30)
+
+ state.scrollBy(-5.dp)
+
+ state.scrollBy(itemSizeDp / 2)
+
+ rule.onNodeWithTag("19").assertMainAxisStartPositionInRootIsEqualTo(itemSizeDp * 3f)
+ }
}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
index 499c99d..b84ee4c 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
@@ -18,7 +18,6 @@
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.animate
-import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.AnchoredDraggableDefaults
@@ -179,52 +178,6 @@
@Preview
@Composable
-fun AnchoredDraggableCatchAnimatingWidgetSample() {
- // Attempting to press the box while it is settling to one anchor won't stop the box from
- // animating to that anchor. If you want to catch it while it is animating, you need to press
- // the box and drag it past the touchSlop. This is because startDragImmediately is set to false.
- val state =
- rememberSaveable(saver = AnchoredDraggableState.Saver()) {
- AnchoredDraggableState(initialValue = Start)
- }
- val density = LocalDensity.current
- val draggableSize = 100.dp
- val draggableSizePx = with(density) { draggableSize.toPx() }
- Box(
- Modifier.fillMaxWidth().onSizeChanged { layoutSize ->
- val dragEndPoint = layoutSize.width - draggableSizePx
- state.updateAnchors(
- DraggableAnchors {
- Start at 0f
- End at dragEndPoint
- }
- )
- }
- ) {
- Box(
- Modifier.size(draggableSize)
- .offset { IntOffset(x = state.requireOffset().roundToInt(), y = 0) }
- .anchoredDraggable(
- state = state,
- orientation = Orientation.Horizontal,
- startDragImmediately = false,
- flingBehavior =
- AnchoredDraggableDefaults.flingBehavior(
- state,
- positionalThreshold = { with(density) { 56.dp.toPx() } },
- // Setting the duration of the snapAnimationSpec to 3000ms gives more
- // time
- // to attempt to press or drag the settling box.
- animationSpec = tween(durationMillis = 3000)
- )
- )
- .background(Color.Red)
- )
- }
-}
-
-@Preview
-@Composable
fun AnchoredDraggableWithOverscrollSample() {
val state =
rememberSaveable(saver = AnchoredDraggableState.Saver()) {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableBackwardsCompatibleTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableBackwardsCompatibleTest.kt
index b11ce3a..e8dd00d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableBackwardsCompatibleTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableBackwardsCompatibleTest.kt
@@ -71,8 +71,6 @@
snapAnimationSpec = snapAnimationSpec,
decayAnimationSpec = decayAnimationSpec
)
- // This won't work for snapshot observation but should be ok for tests
- val resolvedStartDragImmediately = startDragImmediately ?: state.isAnimationRunning
val modifier =
if (testNewBehavior) {
val flingBehavior =
@@ -89,7 +87,7 @@
enabled = enabled,
interactionSource = interactionSource,
overscrollEffect = overscrollEffect,
- startDragImmediately = resolvedStartDragImmediately,
+ startDragImmediately = startDragImmediately,
flingBehavior = flingBehavior
)
} else {
@@ -100,7 +98,7 @@
enabled = enabled,
interactionSource = interactionSource,
overscrollEffect = overscrollEffect,
- startDragImmediately = resolvedStartDragImmediately,
+ startDragImmediately = startDragImmediately,
flingBehavior = null
)
}
@@ -168,31 +166,58 @@
enabled: Boolean = true,
interactionSource: MutableInteractionSource? = null,
overscrollEffect: OverscrollEffect? = null,
- startDragImmediately: Boolean = state.isAnimationRunning,
+ startDragImmediately: Boolean? = null,
flingBehavior: FlingBehavior? = null
- ) =
+ ): Modifier =
when (reverseDirection) {
null ->
- Modifier.anchoredDraggable(
- state = state,
- orientation = orientation,
- enabled = enabled,
- interactionSource = interactionSource,
- overscrollEffect = overscrollEffect,
- startDragImmediately = startDragImmediately,
- flingBehavior = flingBehavior
- )
+ when (startDragImmediately) {
+ null ->
+ Modifier.anchoredDraggable(
+ state = state,
+ orientation = orientation,
+ enabled = enabled,
+ interactionSource = interactionSource,
+ overscrollEffect = overscrollEffect,
+ flingBehavior = flingBehavior
+ )
+ else ->
+ @Suppress("DEPRECATION")
+ Modifier.anchoredDraggable(
+ state = state,
+ orientation = orientation,
+ enabled = enabled,
+ interactionSource = interactionSource,
+ overscrollEffect = overscrollEffect,
+ startDragImmediately = startDragImmediately,
+ flingBehavior = flingBehavior
+ )
+ }
else ->
- Modifier.anchoredDraggable(
- state = state,
- reverseDirection = reverseDirection,
- orientation = orientation,
- enabled = enabled,
- interactionSource = interactionSource,
- overscrollEffect = overscrollEffect,
- startDragImmediately = startDragImmediately,
- flingBehavior = flingBehavior
- )
+ when (startDragImmediately) {
+ null ->
+ Modifier.anchoredDraggable(
+ state = state,
+ reverseDirection = reverseDirection,
+ orientation = orientation,
+ enabled = enabled,
+ interactionSource = interactionSource,
+ overscrollEffect = overscrollEffect,
+ flingBehavior = flingBehavior
+ )
+ else ->
+ @Suppress("DEPRECATION")
+ Modifier.anchoredDraggable(
+ state = state,
+ reverseDirection = reverseDirection,
+ orientation = orientation,
+ enabled = enabled,
+ interactionSource = interactionSource,
+ overscrollEffect = overscrollEffect,
+ startDragImmediately = startDragImmediately,
+ flingBehavior = flingBehavior
+ )
+ }
}
/**
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
index 97adac1..6e7154a 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
@@ -493,8 +493,9 @@
assertThat(state.targetValue).isEqualTo(B)
}
+ // TODO(b/360835763): Remove when removing the old overload
@Test
- fun anchoredDraggable_animationNotCancelledByDrag_startDragImmediatelyIsFalse() {
+ fun anchoredDraggable_startDragImmediately_false_animationNotCancelledByDrag() {
rule.mainClock.autoAdvance = false
val anchors = DraggableAnchors {
A at 0f
@@ -540,6 +541,52 @@
}
@Test
+ fun anchoredDraggable_startDragImmediately_default_processesWithoutSlopWhileAnimating() {
+ rule.mainClock.autoAdvance = false
+ val anchors = DraggableAnchors {
+ A at 0f
+ B at 250f
+ C at 500f
+ }
+ val (state, modifier) =
+ createStateAndModifier(
+ initialValue = A,
+ anchors = anchors,
+ orientation = Orientation.Horizontal,
+ )
+ lateinit var scope: CoroutineScope
+ rule.setContent {
+ WithTouchSlop(touchSlop = 5000f) {
+ scope = rememberCoroutineScope()
+ Box(Modifier.fillMaxSize()) {
+ Box(
+ Modifier.requiredSize(AnchoredDraggableBoxSize)
+ .testTag(AnchoredDraggableTestTag)
+ .then(modifier)
+ .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
+ .background(Color.Red)
+ )
+ }
+ }
+ }
+ assertThat(state.currentValue).isEqualTo(A)
+ assertThat(state.targetValue).isEqualTo(A)
+
+ scope.launch { state.animateTo(C) }
+
+ rule.mainClock.advanceTimeUntil { state.requireOffset() > 10 }
+ val offsetBeforeTouch = state.requireOffset()
+
+ rule.onNodeWithTag(AnchoredDraggableTestTag).performTouchInput {
+ down(Offset.Zero)
+ moveBy(Offset(x = 15f, y = 0f))
+ }
+ // rule.mainClock.advanceTimeByFrame()
+ assertThat(state.requireOffset()).isEqualTo(offsetBeforeTouch + 15f)
+ rule.waitForIdle()
+ }
+
+ @Test
fun anchoredDraggable_updatesState() {
val state1 =
createAnchoredDraggableState(
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/res/drawable-hdpi/ic_image_test.jpg b/compose/foundation/foundation/src/androidInstrumentedTest/res/drawable-hdpi/ic_image_test.jpg
new file mode 100644
index 0000000..628b50a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/res/drawable-hdpi/ic_image_test.jpg
Binary files differ
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/res/drawable-hdpi/ic_image_test.png b/compose/foundation/foundation/src/androidInstrumentedTest/res/drawable-hdpi/ic_image_test.png
deleted file mode 100644
index 4eb583c..0000000
--- a/compose/foundation/foundation/src/androidInstrumentedTest/res/drawable-hdpi/ic_image_test.png
+++ /dev/null
Binary files differ
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
index fa72269..0881e54 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
@@ -95,9 +95,10 @@
* @param enabled Whether this [anchoredDraggable] is enabled and should react to the user's input.
* @param interactionSource Optional [MutableInteractionSource] that will passed on to the internal
* [Modifier.draggable].
- * @param startDragImmediately when set to false, [draggable] will start dragging only when the
- * gesture crosses the touchSlop. This is useful to prevent users from "catching" an animating
- * widget when pressing on it. See [draggable] to learn more about startDragImmediately.
+ * @param overscrollEffect optional effect to dispatch any excess delta or velocity to. The excess
+ * delta or velocity are a result of dragging/flinging and reaching the bounds. If you provide an
+ * [overscrollEffect], make sure to apply [androidx.compose.foundation.overscroll] to render the
+ * effect as well.
* @param flingBehavior Optionally configure how the anchored draggable performs the fling. By
* default (if passing in null), this will snap to the closest anchor considering the velocity
* thresholds and positional thresholds. See [AnchoredDraggableDefaults.flingBehavior].
@@ -108,7 +109,7 @@
orientation: Orientation,
enabled: Boolean = true,
interactionSource: MutableInteractionSource? = null,
- startDragImmediately: Boolean = state.isAnimationRunning,
+ overscrollEffect: OverscrollEffect? = null,
flingBehavior: FlingBehavior? = null
): Modifier =
this then
@@ -118,52 +119,7 @@
enabled = enabled,
reverseDirection = reverseDirection,
interactionSource = interactionSource,
- overscrollEffect = null,
- startDragImmediately = startDragImmediately,
- flingBehavior = flingBehavior
- )
-
-/**
- * Enable drag gestures between a set of predefined values.
- *
- * When a drag is detected, the offset of the [AnchoredDraggableState] will be updated with the drag
- * delta. If the [orientation] is set to [Orientation.Horizontal] and [LocalLayoutDirection]'s value
- * is [LayoutDirection.Rtl], the drag deltas will be reversed. You should use this offset to move
- * your content accordingly (see [Modifier.offset]). When the drag ends, the offset will be animated
- * to one of the anchors and when that anchor is reached, the value of the [AnchoredDraggableState]
- * will also be updated to the value corresponding to the new anchor.
- *
- * Dragging is constrained between the minimum and maximum anchors.
- *
- * @param state The associated [AnchoredDraggableState].
- * @param orientation The orientation in which the [anchoredDraggable] can be dragged.
- * @param enabled Whether this [anchoredDraggable] is enabled and should react to the user's input.
- * @param interactionSource Optional [MutableInteractionSource] that will passed on to the internal
- * [Modifier.draggable].
- * @param startDragImmediately when set to false, [draggable] will start dragging only when the
- * gesture crosses the touchSlop. This is useful to prevent users from "catching" an animating
- * widget when pressing on it. See [draggable] to learn more about startDragImmediately.
- * @param flingBehavior Optionally configure how the anchored draggable performs the fling. By
- * default (if passing in null), this will snap to the closest anchor considering the velocity
- * thresholds and positional thresholds. See [AnchoredDraggableDefaults.flingBehavior].
- */
-fun <T> Modifier.anchoredDraggable(
- state: AnchoredDraggableState<T>,
- orientation: Orientation,
- enabled: Boolean = true,
- interactionSource: MutableInteractionSource? = null,
- startDragImmediately: Boolean = state.isAnimationRunning,
- flingBehavior: FlingBehavior? = null
-): Modifier =
- this then
- AnchoredDraggableElement(
- state = state,
- orientation = orientation,
- enabled = enabled,
- reverseDirection = null,
- interactionSource = interactionSource,
- overscrollEffect = null,
- startDragImmediately = startDragImmediately,
+ overscrollEffect = overscrollEffect,
flingBehavior = flingBehavior
)
@@ -198,6 +154,7 @@
* default (if passing in null), this will snap to the closest anchor considering the velocity
* thresholds and positional thresholds. See [AnchoredDraggableDefaults.flingBehavior].
*/
+@Deprecated(StartDragImmediatelyDeprecated)
fun <T> Modifier.anchoredDraggable(
state: AnchoredDraggableState<T>,
reverseDirection: Boolean,
@@ -241,6 +198,50 @@
* delta or velocity are a result of dragging/flinging and reaching the bounds. If you provide an
* [overscrollEffect], make sure to apply [androidx.compose.foundation.overscroll] to render the
* effect as well.
+ * @param flingBehavior Optionally configure how the anchored draggable performs the fling. By
+ * default (if passing in null), this will snap to the closest anchor considering the velocity
+ * thresholds and positional thresholds. See [AnchoredDraggableDefaults.flingBehavior].
+ */
+fun <T> Modifier.anchoredDraggable(
+ state: AnchoredDraggableState<T>,
+ orientation: Orientation,
+ enabled: Boolean = true,
+ interactionSource: MutableInteractionSource? = null,
+ overscrollEffect: OverscrollEffect? = null,
+ flingBehavior: FlingBehavior? = null
+): Modifier =
+ this then
+ AnchoredDraggableElement(
+ state = state,
+ orientation = orientation,
+ enabled = enabled,
+ reverseDirection = null,
+ interactionSource = interactionSource,
+ overscrollEffect = overscrollEffect,
+ flingBehavior = flingBehavior
+ )
+
+/**
+ * Enable drag gestures between a set of predefined values.
+ *
+ * When a drag is detected, the offset of the [AnchoredDraggableState] will be updated with the drag
+ * delta. If the [orientation] is set to [Orientation.Horizontal] and [LocalLayoutDirection]'s value
+ * is [LayoutDirection.Rtl], the drag deltas will be reversed. You should use this offset to move
+ * your content accordingly (see [Modifier.offset]). When the drag ends, the offset will be animated
+ * to one of the anchors and when that anchor is reached, the value of the [AnchoredDraggableState]
+ * will also be updated to the value corresponding to the new anchor.
+ *
+ * Dragging is constrained between the minimum and maximum anchors.
+ *
+ * @param state The associated [AnchoredDraggableState].
+ * @param orientation The orientation in which the [anchoredDraggable] can be dragged.
+ * @param enabled Whether this [anchoredDraggable] is enabled and should react to the user's input.
+ * @param interactionSource Optional [MutableInteractionSource] that will passed on to the internal
+ * [Modifier.draggable].
+ * @param overscrollEffect optional effect to dispatch any excess delta or velocity to. The excess
+ * delta or velocity are a result of dragging/flinging and reaching the bounds. If you provide an
+ * [overscrollEffect], make sure to apply [androidx.compose.foundation.overscroll] to render the
+ * effect as well.
* @param startDragImmediately when set to false, [draggable] will start dragging only when the
* gesture crosses the touchSlop. This is useful to prevent users from "catching" an animating
* widget when pressing on it. See [draggable] to learn more about startDragImmediately.
@@ -248,6 +249,7 @@
* default (if passing in null), this will snap to the closest anchor considering the velocity
* thresholds and positional thresholds. See [AnchoredDraggableDefaults.flingBehavior].
*/
+@Deprecated(StartDragImmediatelyDeprecated)
fun <T> Modifier.anchoredDraggable(
state: AnchoredDraggableState<T>,
orientation: Orientation,
@@ -275,7 +277,7 @@
private val enabled: Boolean,
private val reverseDirection: Boolean?,
private val interactionSource: MutableInteractionSource?,
- private val startDragImmediately: Boolean,
+ private val startDragImmediately: Boolean? = null,
private val overscrollEffect: OverscrollEffect?,
private val flingBehavior: FlingBehavior? = null,
) : ModifierNodeElement<AnchoredDraggableNode<T>>() {
@@ -353,7 +355,7 @@
private var reverseDirection: Boolean?,
interactionSource: MutableInteractionSource?,
private var overscrollEffect: OverscrollEffect?,
- private var startDragImmediately: Boolean,
+ private var startDragImmediately: Boolean?,
private var flingBehavior: FlingBehavior?
) :
DragGestureNode(
@@ -470,7 +472,7 @@
leftoverVelocity
}
- override fun startDragImmediately(): Boolean = startDragImmediately
+ override fun startDragImmediately(): Boolean = startDragImmediately ?: state.isAnimationRunning
fun update(
state: AnchoredDraggableState<T>,
@@ -479,7 +481,7 @@
reverseDirection: Boolean?,
interactionSource: MutableInteractionSource?,
overscrollEffect: OverscrollEffect?,
- startDragImmediately: Boolean,
+ startDragImmediately: Boolean?,
flingBehavior: FlingBehavior?,
) {
this.flingBehavior = flingBehavior
@@ -1617,6 +1619,10 @@
"settle does not accept a velocity anymore. " +
"Please use FlingBehavior#performFling instead. See AnchoredDraggableSamples.kt for example " +
"usages."
+private const val StartDragImmediatelyDeprecated =
+ "startDragImmediately has been removed " +
+ "without replacement. Modifier.anchoredDraggable sets startDragImmediately to true by " +
+ "default when animations are running."
/**
* Construct a [FlingBehavior] for use with [Modifier.anchoredDraggable].
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt
index d070bc0..89174a1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt
@@ -168,6 +168,7 @@
) {
return null
}
+ val mainAxisMax = viewportEndOffset - afterContentPadding
visibleItemsInfo.fastForEach {
// non scrollable items require special handling.
if (
@@ -192,7 +193,7 @@
if (!canApply) return null
}
// item is partially visible at the bottom.
- if (it.mainAxisOffset + it.mainAxisSizeWithSpacings >= viewportEndOffset) {
+ if (it.mainAxisOffset + it.mainAxisSizeWithSpacings >= mainAxisMax) {
val canApply =
if (delta < 0) { // scrolling forward
it.mainAxisOffset + it.mainAxisSizeWithSpacings - viewportEndOffset > -delta
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/ComplexNestedListsScrollBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/ComplexNestedListsScrollBenchmark.kt
index ad51c92..a80379b 100644
--- a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/ComplexNestedListsScrollBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/ComplexNestedListsScrollBenchmark.kt
@@ -49,7 +49,7 @@
packageName = PACKAGE_NAME,
metrics = listOf(FrameTimingMetric(), FrameTimingGfxInfoMetric()),
compilationMode = CompilationMode.Full(),
- iterations = 8,
+ iterations = 5,
setupBlock = {
val intent = Intent()
intent.action = ACTION
@@ -59,7 +59,7 @@
val lazyColumn = device.findObject(By.desc(CONTENT_DESCRIPTION))
// Setting a gesture margin is important otherwise gesture nav is triggered.
lazyColumn.setGestureMargin(device.displayWidth / 5)
- for (i in 1..10) {
+ for (i in 1..8) {
// From center we scroll 2/3 of it which is 1/3 of the screen.
lazyColumn.drag(Point(lazyColumn.visibleCenter.x, lazyColumn.visibleCenter.y / 3))
device.wait(Until.findObject(By.desc(COMPOSE_IDLE)), 3000)
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/DifferentTypesListScrollBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/DifferentTypesListScrollBenchmark.kt
index d39fb36..1cc8b56 100644
--- a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/DifferentTypesListScrollBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/DifferentTypesListScrollBenchmark.kt
@@ -51,7 +51,7 @@
packageName = PACKAGE_NAME,
metrics = listOf(FrameTimingMetric(), FrameTimingGfxInfoMetric()),
compilationMode = CompilationMode.Full(),
- iterations = 10,
+ iterations = 5,
setupBlock = {
val intent = Intent()
intent.action = ACTION
@@ -61,7 +61,7 @@
val lazyColumn = device.findObject(By.desc(CONTENT_DESCRIPTION))
// Setting a gesture margin is important otherwise gesture nav is triggered.
lazyColumn.setGestureMargin(device.displayWidth / 5)
- for (i in 1..10) {
+ for (i in 1..8) {
// From center we scroll 2/3 of it which is 1/3 of the screen.
lazyColumn.drag(Point(lazyColumn.visibleCenter.x, lazyColumn.visibleCenter.y / 3))
device.wait(Until.findObject(By.desc(COMPOSE_IDLE)), 3000)
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/VectorsListScrollBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/VectorsListScrollBenchmark.kt
index efac0b9..c62aefd 100644
--- a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/VectorsListScrollBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/VectorsListScrollBenchmark.kt
@@ -48,7 +48,7 @@
packageName = PACKAGE_NAME,
metrics = listOf(FrameTimingMetric()),
compilationMode = CompilationMode.Full(),
- iterations = 8,
+ iterations = 5,
setupBlock = {
val intent = Intent()
intent.action = ACTION
@@ -58,7 +58,7 @@
val lazyColumn = device.findObject(By.desc(CONTENT_DESCRIPTION))
// Setting a gesture margin is important otherwise gesture nav is triggered.
lazyColumn.setGestureMargin(device.displayWidth / 5)
- for (i in 1..10) {
+ for (i in 1..8) {
// From center we scroll 2/3 of it which is 1/3 of the screen.
lazyColumn.drag(Point(0, lazyColumn.visibleCenter.y / 3))
device.wait(Until.findObject(By.desc(COMPOSE_IDLE)), 3000)
diff --git a/compose/material/material-navigation/build.gradle b/compose/material/material-navigation/build.gradle
index f577da2..4cf82b4 100644
--- a/compose/material/material-navigation/build.gradle
+++ b/compose/material/material-navigation/build.gradle
@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -47,6 +49,7 @@
description = "Compose Material integration with Navigation"
legacyDisableKotlinStrictApiMode = true
samples(projectOrArtifact(":compose:material:material-navigation-samples"))
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
android {
diff --git a/compose/material/material-navigation/samples/build.gradle b/compose/material/material-navigation/samples/build.gradle
index 20b3d14d..0e1cdc4 100644
--- a/compose/material/material-navigation/samples/build.gradle
+++ b/compose/material/material-navigation/samples/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -45,6 +47,7 @@
mavenVersion = LibraryVersions.COMPOSE
inceptionYear = "2024"
description = "Samples for Compose Material integration with Navigation"
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
android {
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index 561ce63..a64ea1b 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -14,6 +14,12 @@
public sealed interface AnimatedPaneScope extends androidx.compose.animation.AnimatedVisibilityScope {
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ExtendedPaneScaffoldPaneScope<Role, ScaffoldValue extends androidx.compose.material3.adaptive.layout.PaneScaffoldValue<Role>> extends androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldScope<Role,ScaffoldValue> androidx.compose.material3.adaptive.layout.PaneScaffoldPaneScope<Role> {
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ExtendedPaneScaffoldScope<Role, ScaffoldValue extends androidx.compose.material3.adaptive.layout.PaneScaffoldValue<Role>> extends androidx.compose.material3.adaptive.layout.PaneScaffoldScope androidx.compose.ui.layout.LookaheadScope androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope androidx.compose.material3.adaptive.layout.PaneScaffoldTransitionScope<Role,ScaffoldValue> {
+ }
+
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class HingePolicy {
field public static final androidx.compose.material3.adaptive.layout.HingePolicy.Companion Companion;
}
@@ -35,8 +41,8 @@
}
public final class ListDetailPaneScaffoldKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
}
public final class ListDetailPaneScaffoldRole {
@@ -113,7 +119,25 @@
}
public final class PaneKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <S, T extends androidx.compose.material3.adaptive.layout.PaneScaffoldValue<S>> void AnimatedPane(androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldPaneScope<S,T>, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content);
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneMotion {
+ method public androidx.compose.animation.EnterTransition getEnterTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
+ method public androidx.compose.animation.ExitTransition getExitTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class PaneMotionData {
+ method public long getCurrentPosition();
+ method public long getCurrentSize();
+ method public androidx.compose.material3.adaptive.layout.PaneMotion getMotion();
+ method public long getTargetPosition();
+ method public long getTargetSize();
+ property public final long currentPosition;
+ property public final long currentSize;
+ property public final androidx.compose.material3.adaptive.layout.PaneMotion motion;
+ property public final long targetPosition;
+ property public final long targetSize;
}
@androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
@@ -144,18 +168,49 @@
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldMotionScope {
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getDelayedPositionAnimationSpec();
+ method public java.util.List<androidx.compose.material3.adaptive.layout.PaneMotionData> getPaneMotionDataList();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getPositionAnimationSpec();
+ method public long getScaffoldSize();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> getSizeAnimationSpec();
+ property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> delayedPositionAnimationSpec;
+ property public abstract java.util.List<androidx.compose.material3.adaptive.layout.PaneMotionData> paneMotionDataList;
+ property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> positionAnimationSpec;
+ property public abstract long scaffoldSize;
+ property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldPaneScope<Role> {
+ method public androidx.compose.material3.adaptive.layout.PaneMotion getPaneMotion();
+ method public Role getPaneRole();
+ property public abstract androidx.compose.material3.adaptive.layout.PaneMotion paneMotion;
+ property public abstract Role paneRole;
+ }
+
public sealed interface PaneScaffoldScope {
method public androidx.compose.ui.Modifier preferredWidth(androidx.compose.ui.Modifier, float width);
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldTransitionScope<Role, ScaffoldValue extends androidx.compose.material3.adaptive.layout.PaneScaffoldValue<Role>> {
+ method public float getMotionProgress();
+ method public androidx.compose.animation.core.Transition<ScaffoldValue> getScaffoldStateTransition();
+ property public abstract float motionProgress;
+ property public abstract androidx.compose.animation.core.Transition<ScaffoldValue> scaffoldStateTransition;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldValue<T> {
+ method public operator String get(T role);
+ }
+
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldDefaults {
method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.layout.AdaptStrategy mainPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy supportingPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy extraPaneAdaptStrategy);
field public static final androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldDefaults INSTANCE;
}
public final class SupportingPaneScaffoldKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
@@ -168,43 +223,67 @@
field public static final androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole INSTANCE;
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneMotion {
+ ctor public ThreePaneMotion(androidx.compose.material3.adaptive.layout.PaneMotion primaryPaneMotion, androidx.compose.material3.adaptive.layout.PaneMotion secondaryPaneMotion, androidx.compose.material3.adaptive.layout.PaneMotion tertiaryPaneMotion, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> positionAnimationSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> delayedPositionAnimationSpec);
+ method public androidx.compose.material3.adaptive.layout.ThreePaneMotion copy(optional androidx.compose.material3.adaptive.layout.PaneMotion primaryPaneMotion, optional androidx.compose.material3.adaptive.layout.PaneMotion secondaryPaneMotion, optional androidx.compose.material3.adaptive.layout.PaneMotion tertiaryPaneMotion, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> positionAnimationSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> delayedPositionAnimationSpec);
+ method public operator androidx.compose.material3.adaptive.layout.PaneMotion get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getDelayedPositionAnimationSpec();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getPositionAnimationSpec();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> getSizeAnimationSpec();
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> delayedPositionAnimationSpec;
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> positionAnimationSpec;
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec;
+ field public static final androidx.compose.material3.adaptive.layout.ThreePaneMotion.Companion Companion;
+ }
+
+ public static final class ThreePaneMotion.Companion {
+ method public androidx.compose.material3.adaptive.layout.ThreePaneMotion getNoMotion();
+ property public final androidx.compose.material3.adaptive.layout.ThreePaneMotion NoMotion;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneMotionDefaults {
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getPanePositionAnimationSpec();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getPanePositionAnimationSpecDelayed();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> getPaneSizeAnimationSpec();
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> PanePositionAnimationSpec;
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> PanePositionAnimationSpecDelayed;
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> PaneSizeAnimationSpec;
+ field public static final androidx.compose.material3.adaptive.layout.ThreePaneMotionDefaults INSTANCE;
+ }
+
+ public final class ThreePaneMotionKt {
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneMotion calculateListDetailPaneScaffoldMotion(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneMotion calculateListDetailPaneScaffoldMotion(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetScaffoldValue);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneMotion calculateSupportingPaneScaffoldMotion(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneMotion calculateSupportingPaneScaffoldMotion(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetScaffoldValue);
+ }
+
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldAdaptStrategies {
ctor public ThreePaneScaffoldAdaptStrategies(androidx.compose.material3.adaptive.layout.AdaptStrategy primaryPaneAdaptStrategy, androidx.compose.material3.adaptive.layout.AdaptStrategy secondaryPaneAdaptStrategy, androidx.compose.material3.adaptive.layout.AdaptStrategy tertiaryPaneAdaptStrategy);
method public operator androidx.compose.material3.adaptive.layout.AdaptStrategy get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldDestinationItem<T> {
- ctor public ThreePaneScaffoldDestinationItem(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? content);
- method public T? getContent();
+ ctor public ThreePaneScaffoldDestinationItem(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? contentKey);
+ method public T? getContentKey();
method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getPane();
- property public final T? content;
+ property public final T? contentKey;
property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane;
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ThreePaneScaffoldPaneScope extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldPaneScope<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> {
+ }
+
public enum ThreePaneScaffoldRole {
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Primary;
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Secondary;
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Tertiary;
}
- @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ThreePaneScaffoldScope extends androidx.compose.material3.adaptive.layout.PaneScaffoldScope androidx.compose.ui.layout.LookaheadScope {
- method public androidx.compose.animation.EnterTransition getEnterTransition();
- method public androidx.compose.animation.ExitTransition getExitTransition();
- method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getPositionAnimationSpec();
- method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getRole();
- method public androidx.compose.animation.core.Transition<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> getScaffoldStateTransition();
- method public float getScaffoldStateTransitionFraction();
- method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> getSizeAnimationSpec();
- property public abstract androidx.compose.animation.EnterTransition enterTransition;
- property public abstract androidx.compose.animation.ExitTransition exitTransition;
- property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> positionAnimationSpec;
- property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role;
- property public abstract androidx.compose.animation.core.Transition<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> scaffoldStateTransition;
- property public abstract float scaffoldStateTransitionFraction;
- property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec;
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ThreePaneScaffoldScope extends androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldScope<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> {
}
- @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldState {
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class ThreePaneScaffoldState {
ctor public ThreePaneScaffoldState(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue initialScaffoldValue);
method public suspend Object? animateTo(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getCurrentState();
@@ -217,7 +296,7 @@
property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState;
}
- @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue implements androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider {
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue implements androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider androidx.compose.material3.adaptive.layout.PaneScaffoldValue<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole> {
ctor public ThreePaneScaffoldValue(String primary, String secondary, String tertiary);
method public operator String get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
method public androidx.compose.material3.adaptive.layout.PaneExpansionStateKey getPaneExpansionStateKey();
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index 561ce63..a64ea1b 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -14,6 +14,12 @@
public sealed interface AnimatedPaneScope extends androidx.compose.animation.AnimatedVisibilityScope {
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ExtendedPaneScaffoldPaneScope<Role, ScaffoldValue extends androidx.compose.material3.adaptive.layout.PaneScaffoldValue<Role>> extends androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldScope<Role,ScaffoldValue> androidx.compose.material3.adaptive.layout.PaneScaffoldPaneScope<Role> {
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ExtendedPaneScaffoldScope<Role, ScaffoldValue extends androidx.compose.material3.adaptive.layout.PaneScaffoldValue<Role>> extends androidx.compose.material3.adaptive.layout.PaneScaffoldScope androidx.compose.ui.layout.LookaheadScope androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope androidx.compose.material3.adaptive.layout.PaneScaffoldTransitionScope<Role,ScaffoldValue> {
+ }
+
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class HingePolicy {
field public static final androidx.compose.material3.adaptive.layout.HingePolicy.Companion Companion;
}
@@ -35,8 +41,8 @@
}
public final class ListDetailPaneScaffoldKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
}
public final class ListDetailPaneScaffoldRole {
@@ -113,7 +119,25 @@
}
public final class PaneKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <S, T extends androidx.compose.material3.adaptive.layout.PaneScaffoldValue<S>> void AnimatedPane(androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldPaneScope<S,T>, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content);
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneMotion {
+ method public androidx.compose.animation.EnterTransition getEnterTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
+ method public androidx.compose.animation.ExitTransition getExitTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class PaneMotionData {
+ method public long getCurrentPosition();
+ method public long getCurrentSize();
+ method public androidx.compose.material3.adaptive.layout.PaneMotion getMotion();
+ method public long getTargetPosition();
+ method public long getTargetSize();
+ property public final long currentPosition;
+ property public final long currentSize;
+ property public final androidx.compose.material3.adaptive.layout.PaneMotion motion;
+ property public final long targetPosition;
+ property public final long targetSize;
}
@androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
@@ -144,18 +168,49 @@
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldMotionScope {
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getDelayedPositionAnimationSpec();
+ method public java.util.List<androidx.compose.material3.adaptive.layout.PaneMotionData> getPaneMotionDataList();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getPositionAnimationSpec();
+ method public long getScaffoldSize();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> getSizeAnimationSpec();
+ property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> delayedPositionAnimationSpec;
+ property public abstract java.util.List<androidx.compose.material3.adaptive.layout.PaneMotionData> paneMotionDataList;
+ property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> positionAnimationSpec;
+ property public abstract long scaffoldSize;
+ property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldPaneScope<Role> {
+ method public androidx.compose.material3.adaptive.layout.PaneMotion getPaneMotion();
+ method public Role getPaneRole();
+ property public abstract androidx.compose.material3.adaptive.layout.PaneMotion paneMotion;
+ property public abstract Role paneRole;
+ }
+
public sealed interface PaneScaffoldScope {
method public androidx.compose.ui.Modifier preferredWidth(androidx.compose.ui.Modifier, float width);
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldTransitionScope<Role, ScaffoldValue extends androidx.compose.material3.adaptive.layout.PaneScaffoldValue<Role>> {
+ method public float getMotionProgress();
+ method public androidx.compose.animation.core.Transition<ScaffoldValue> getScaffoldStateTransition();
+ property public abstract float motionProgress;
+ property public abstract androidx.compose.animation.core.Transition<ScaffoldValue> scaffoldStateTransition;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldValue<T> {
+ method public operator String get(T role);
+ }
+
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldDefaults {
method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.layout.AdaptStrategy mainPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy supportingPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy extraPaneAdaptStrategy);
field public static final androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldDefaults INSTANCE;
}
public final class SupportingPaneScaffoldKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
@@ -168,43 +223,67 @@
field public static final androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole INSTANCE;
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneMotion {
+ ctor public ThreePaneMotion(androidx.compose.material3.adaptive.layout.PaneMotion primaryPaneMotion, androidx.compose.material3.adaptive.layout.PaneMotion secondaryPaneMotion, androidx.compose.material3.adaptive.layout.PaneMotion tertiaryPaneMotion, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> positionAnimationSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> delayedPositionAnimationSpec);
+ method public androidx.compose.material3.adaptive.layout.ThreePaneMotion copy(optional androidx.compose.material3.adaptive.layout.PaneMotion primaryPaneMotion, optional androidx.compose.material3.adaptive.layout.PaneMotion secondaryPaneMotion, optional androidx.compose.material3.adaptive.layout.PaneMotion tertiaryPaneMotion, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> positionAnimationSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> delayedPositionAnimationSpec);
+ method public operator androidx.compose.material3.adaptive.layout.PaneMotion get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getDelayedPositionAnimationSpec();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getPositionAnimationSpec();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> getSizeAnimationSpec();
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> delayedPositionAnimationSpec;
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> positionAnimationSpec;
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec;
+ field public static final androidx.compose.material3.adaptive.layout.ThreePaneMotion.Companion Companion;
+ }
+
+ public static final class ThreePaneMotion.Companion {
+ method public androidx.compose.material3.adaptive.layout.ThreePaneMotion getNoMotion();
+ property public final androidx.compose.material3.adaptive.layout.ThreePaneMotion NoMotion;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneMotionDefaults {
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getPanePositionAnimationSpec();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getPanePositionAnimationSpecDelayed();
+ method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> getPaneSizeAnimationSpec();
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> PanePositionAnimationSpec;
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> PanePositionAnimationSpecDelayed;
+ property public final androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> PaneSizeAnimationSpec;
+ field public static final androidx.compose.material3.adaptive.layout.ThreePaneMotionDefaults INSTANCE;
+ }
+
+ public final class ThreePaneMotionKt {
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneMotion calculateListDetailPaneScaffoldMotion(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneMotion calculateListDetailPaneScaffoldMotion(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetScaffoldValue);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneMotion calculateSupportingPaneScaffoldMotion(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.ThreePaneMotion calculateSupportingPaneScaffoldMotion(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetScaffoldValue);
+ }
+
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldAdaptStrategies {
ctor public ThreePaneScaffoldAdaptStrategies(androidx.compose.material3.adaptive.layout.AdaptStrategy primaryPaneAdaptStrategy, androidx.compose.material3.adaptive.layout.AdaptStrategy secondaryPaneAdaptStrategy, androidx.compose.material3.adaptive.layout.AdaptStrategy tertiaryPaneAdaptStrategy);
method public operator androidx.compose.material3.adaptive.layout.AdaptStrategy get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldDestinationItem<T> {
- ctor public ThreePaneScaffoldDestinationItem(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? content);
- method public T? getContent();
+ ctor public ThreePaneScaffoldDestinationItem(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? contentKey);
+ method public T? getContentKey();
method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getPane();
- property public final T? content;
+ property public final T? contentKey;
property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane;
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ThreePaneScaffoldPaneScope extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldPaneScope<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> {
+ }
+
public enum ThreePaneScaffoldRole {
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Primary;
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Secondary;
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Tertiary;
}
- @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ThreePaneScaffoldScope extends androidx.compose.material3.adaptive.layout.PaneScaffoldScope androidx.compose.ui.layout.LookaheadScope {
- method public androidx.compose.animation.EnterTransition getEnterTransition();
- method public androidx.compose.animation.ExitTransition getExitTransition();
- method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> getPositionAnimationSpec();
- method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getRole();
- method public androidx.compose.animation.core.Transition<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> getScaffoldStateTransition();
- method public float getScaffoldStateTransitionFraction();
- method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> getSizeAnimationSpec();
- property public abstract androidx.compose.animation.EnterTransition enterTransition;
- property public abstract androidx.compose.animation.ExitTransition exitTransition;
- property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> positionAnimationSpec;
- property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role;
- property public abstract androidx.compose.animation.core.Transition<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> scaffoldStateTransition;
- property public abstract float scaffoldStateTransitionFraction;
- property public abstract androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> sizeAnimationSpec;
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ThreePaneScaffoldScope extends androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldScope<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> {
}
- @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldState {
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class ThreePaneScaffoldState {
ctor public ThreePaneScaffoldState(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue initialScaffoldValue);
method public suspend Object? animateTo(optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getCurrentState();
@@ -217,7 +296,7 @@
property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue targetState;
}
- @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue implements androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider {
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldValue implements androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider androidx.compose.material3.adaptive.layout.PaneScaffoldValue<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole> {
ctor public ThreePaneScaffoldValue(String primary, String secondary, String tertiary);
method public operator String get(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
method public androidx.compose.material3.adaptive.layout.PaneExpansionStateKey getPaneExpansionStateKey();
diff --git a/compose/material3/adaptive/adaptive-layout/build.gradle b/compose/material3/adaptive/adaptive-layout/build.gradle
index 9400e45..f37fbdb 100644
--- a/compose/material3/adaptive/adaptive-layout/build.gradle
+++ b/compose/material3/adaptive/adaptive-layout/build.gradle
@@ -42,14 +42,14 @@
dependencies {
implementation(libs.kotlinStdlib)
api(project(":compose:material3:adaptive:adaptive"))
- api("androidx.compose.animation:animation-core:1.7.0-rc01")
- api("androidx.compose.ui:ui:1.7.0-rc01")
- implementation("androidx.compose.animation:animation:1.7.0-rc01")
+ api("androidx.compose.animation:animation-core:1.7.0")
+ api("androidx.compose.ui:ui:1.7.0")
+ implementation("androidx.compose.animation:animation:1.7.0")
implementation("androidx.compose.foundation:foundation:1.6.5")
implementation("androidx.compose.foundation:foundation-layout:1.6.5")
implementation("androidx.compose.ui:ui-geometry:1.6.5")
implementation("androidx.compose.ui:ui-util:1.6.5")
- implementation("androidx.window:window-core:1.3.0-rc01")
+ implementation("androidx.window:window-core:1.3.0")
}
}
@@ -83,7 +83,7 @@
dependencies {
implementation(project(":compose:material3:material3"))
implementation(project(":compose:test-utils"))
- implementation(project(":window:window-testing"))
+ implementation("androidx.window:window-testing:1.3.0")
implementation(libs.junit)
implementation(libs.testRunner)
implementation(libs.truth)
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldMotionScreenshotTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldMotionScreenshotTest.kt
new file mode 100644
index 0000000..4626533
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldMotionScreenshotTest.kt
@@ -0,0 +1,418 @@
+/*
+ * Copyright 2024 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.adaptive.layout
+
+import android.os.Build
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+class ThreePaneScaffoldMotionScreenshotTest {
+ @get:Rule val rule = createComposeRule()
+
+ @get:Rule val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3_ADAPTIVE)
+
+ @Test
+ fun singlePaneLayout_defaultPaneMotion_progress0() {
+ rule.setContent {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(0f, MockTargetScaffoldValueSinglePane) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "singlePaneLayout_defaultPaneMotion_progress0")
+ }
+
+ @Test
+ fun singlePaneLayout_defaultPaneMotion_progress10() {
+ rule.setContent {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(0.1f, MockTargetScaffoldValueSinglePane) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "singlePaneLayout_defaultPaneMotion_progress10")
+ }
+
+ @Test
+ fun singlePaneLayout_defaultPaneMotion_progress15() {
+ rule.setContent {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(0.15f, MockTargetScaffoldValueSinglePane) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "singlePaneLayout_defaultPaneMotion_progress15")
+ }
+
+ @Test
+ fun singlePaneLayout_defaultPaneMotion_progress20() {
+ rule.setContent {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(0.2f, MockTargetScaffoldValueSinglePane) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "singlePaneLayout_defaultPaneMotion_progress20")
+ }
+
+ @Test
+ fun singlePaneLayout_defaultPaneMotion_progress50() {
+ rule.setContent {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(0.5f, MockTargetScaffoldValueSinglePane) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "singlePaneLayout_defaultPaneMotion_progress50")
+ }
+
+ @Test
+ fun singlePaneLayout_defaultPaneMotion_progress100() {
+ rule.setContent {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueSinglePane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(1f, MockTargetScaffoldValueSinglePane) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "singlePaneLayout_defaultPaneMotion_progress100")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneSwitching_progress0() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(0f, MockTargetScaffoldValuePaneSwitching) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneSwitching_progress0")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneSwitching_progress10() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) {
+ scaffoldState.seekTo(0.1f, MockTargetScaffoldValuePaneSwitching)
+ }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneSwitching_progress10")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneSwitching_progress15() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) {
+ scaffoldState.seekTo(0.15f, MockTargetScaffoldValuePaneSwitching)
+ }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneSwitching_progress15")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneSwitching_progress20() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) {
+ scaffoldState.seekTo(0.2f, MockTargetScaffoldValuePaneSwitching)
+ }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneSwitching_progress20")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneSwitching_progress50() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) {
+ scaffoldState.seekTo(0.5f, MockTargetScaffoldValuePaneSwitching)
+ }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneSwitching_progress50")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneSwitching_progress100() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(1f, MockTargetScaffoldValuePaneSwitching) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneSwitching_progress100")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneShifting_progress0() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(0f, MockTargetScaffoldValuePaneShifting) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneShifting_progress0")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneShifting_progress10() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(0.1f, MockTargetScaffoldValuePaneShifting) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneShifting_progress10")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneShifting_progress15() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) {
+ scaffoldState.seekTo(0.15f, MockTargetScaffoldValuePaneShifting)
+ }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneShifting_progress15")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneShifting_progress20() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(0.2f, MockTargetScaffoldValuePaneShifting) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneShifting_progress20")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneShifting_progress50() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(0.5f, MockTargetScaffoldValuePaneShifting) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneShifting_progress50")
+ }
+
+ @Test
+ fun dualPaneLayout_defaultPaneShifting_progress100() {
+ rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
+ val scaffoldState = ThreePaneScaffoldState(MockOriginalScaffoldValueDualPane)
+ SampleThreePaneScaffold(scaffoldState)
+ LaunchedEffect(Unit) { scaffoldState.seekTo(1f, MockTargetScaffoldValuePaneShifting) }
+ }
+
+ rule.waitForIdle()
+
+ rule
+ .onNodeWithTag(ThreePaneScaffoldTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "dualPaneLayout_defaultPaneShifting_progress100")
+ }
+
+ companion object {
+ val MockOriginalScaffoldValueSinglePane =
+ ThreePaneScaffoldValue(
+ PaneAdaptedValue.Expanded,
+ PaneAdaptedValue.Hidden,
+ PaneAdaptedValue.Hidden,
+ )
+
+ val MockTargetScaffoldValueSinglePane =
+ ThreePaneScaffoldValue(
+ PaneAdaptedValue.Hidden,
+ PaneAdaptedValue.Expanded,
+ PaneAdaptedValue.Hidden,
+ )
+
+ val MockOriginalScaffoldValueDualPane =
+ ThreePaneScaffoldValue(
+ PaneAdaptedValue.Expanded,
+ PaneAdaptedValue.Expanded,
+ PaneAdaptedValue.Hidden,
+ )
+
+ val MockTargetScaffoldValuePaneSwitching =
+ ThreePaneScaffoldValue(
+ PaneAdaptedValue.Expanded,
+ PaneAdaptedValue.Hidden,
+ PaneAdaptedValue.Expanded,
+ )
+
+ val MockTargetScaffoldValuePaneShifting =
+ ThreePaneScaffoldValue(
+ PaneAdaptedValue.Hidden,
+ PaneAdaptedValue.Expanded,
+ PaneAdaptedValue.Expanded,
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Composable
+internal fun SampleThreePaneScaffold(
+ scaffoldState: ThreePaneScaffoldState,
+) {
+ ThreePaneScaffold(
+ modifier = Modifier.fillMaxSize().testTag(ThreePaneScaffoldTestTag),
+ scaffoldDirective = calculatePaneScaffoldDirective(currentWindowAdaptiveInfo()),
+ scaffoldState = scaffoldState,
+ paneOrder = SupportingPaneScaffoldDefaults.PaneOrder,
+ secondaryPane = {
+ AnimatedPane(modifier = Modifier.testTag(tag = "SecondaryPane")) {
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.secondary
+ ) {}
+ }
+ },
+ tertiaryPane = {
+ AnimatedPane(modifier = Modifier.testTag(tag = "TertiaryPane")) {
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.tertiary
+ ) {}
+ }
+ }
+ ) {
+ AnimatedPane(modifier = Modifier.testTag(tag = "PrimaryPane")) {
+ Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.primary) {}
+ }
+ }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/DelayedSpringSpecTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/DelayedSpringSpecTest.kt
new file mode 100644
index 0000000..da4040e
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/DelayedSpringSpecTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2024 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.adaptive.layout
+
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.VectorizedAnimationSpec
+import androidx.compose.animation.core.spring
+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 DelayedSpringSpecTest {
+ @Test
+ fun delayedSpring_identicalWithOriginPlusDelay() {
+ val delayedRatio = 0.5f
+
+ val originalSpec =
+ spring(dampingRatio = 0.7f, stiffness = 500f, visibilityThreshold = 0.1f)
+ .vectorize(Float.VectorConverter)
+
+ val delayedSpec =
+ DelayedSpringSpec(
+ dampingRatio = 0.7f,
+ stiffness = 500f,
+ visibilityThreshold = 0.1f,
+ delayedRatio = delayedRatio,
+ )
+ .vectorize(Float.VectorConverter)
+
+ val originalDurationNanos = originalSpec.getDurationNanos()
+ val delayedNanos = (originalDurationNanos * delayedRatio).toLong()
+
+ fun assertValuesAt(playTimeNanos: Long) {
+ assertValuesAreEqual(
+ originalSpec.getValueFromNanos(playTimeNanos),
+ delayedSpec.getValueFromNanos(playTimeNanos + delayedNanos)
+ )
+ }
+
+ assertValuesAt(0)
+ assertValuesAt((originalDurationNanos * 0.2).toLong())
+ assertValuesAt((originalDurationNanos * 0.35).toLong())
+ assertValuesAt((originalDurationNanos * 0.6).toLong())
+ assertValuesAt((originalDurationNanos * 0.85).toLong())
+ assertValuesAt(originalDurationNanos)
+ }
+
+ private fun VectorizedAnimationSpec<AnimationVector1D>.getDurationNanos(): Long =
+ getDurationNanos(InitialValue, TargetValue, InitialVelocity)
+
+ private fun VectorizedAnimationSpec<AnimationVector1D>.getValueFromNanos(
+ playTimeNanos: Long
+ ): Float = getValueFromNanos(playTimeNanos, InitialValue, TargetValue, InitialVelocity).value
+
+ private fun assertValuesAreEqual(value1: Float, value2: Float) {
+ assertThat(value1).isWithin(Tolerance).of(value2)
+ }
+
+ companion object {
+ private val InitialValue = AnimationVector1D(0f)
+ private val TargetValue = AnimationVector1D(1f)
+ private val InitialVelocity = AnimationVector1D(0f)
+ private const val Tolerance = 0.001f
+ }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt
index 35d588d..10d5ba2 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt
@@ -18,10 +18,6 @@
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.snap
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.core.tween
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.animation.slideInHorizontally
@@ -98,93 +94,93 @@
@Test
fun slideInFromLeftOffset_noEnterFromLeftPane_equalsZero() {
- mockPaneScaffoldMotionScope.paneMotions =
- listOf(EnterFromRight, EnterFromRight, EnterFromRight)
+ mockPaneScaffoldMotionScope.updateMotions(EnterFromRight, EnterFromRight, EnterFromRight)
assertThat(mockPaneScaffoldMotionScope.slideInFromLeftOffset).isEqualTo(0)
}
@Test
fun slideInFromLeftOffset_withEnterFromLeftPane_useTheRightestEdge() {
- mockPaneScaffoldMotionScope.paneMotions =
- listOf(EnterFromLeft, EnterFromLeft, EnterFromRight)
+ mockPaneScaffoldMotionScope.updateMotions(EnterFromLeft, EnterFromLeft, EnterFromRight)
assertThat(mockPaneScaffoldMotionScope.slideInFromLeftOffset)
.isEqualTo(
- -mockPaneScaffoldMotionScope.targetPanePositions[1].x -
- mockPaneScaffoldMotionScope.targetPaneSizes[1].width
+ -mockPaneScaffoldMotionScope.paneMotionDataList[1].targetPosition.x -
+ mockPaneScaffoldMotionScope.paneMotionDataList[1].targetSize.width
)
}
@Test
fun slideInFromLeftOffset_withEnterFromLeftDelayedPane_useTheRightestEdge() {
- mockPaneScaffoldMotionScope.paneMotions =
- listOf(EnterFromLeft, EnterFromLeftDelayed, EnterFromRight)
+ mockPaneScaffoldMotionScope.updateMotions(
+ EnterFromLeft,
+ EnterFromLeftDelayed,
+ EnterFromRight
+ )
assertThat(mockPaneScaffoldMotionScope.slideInFromLeftOffset)
.isEqualTo(
- -mockPaneScaffoldMotionScope.targetPanePositions[1].x -
- mockPaneScaffoldMotionScope.targetPaneSizes[1].width
+ -mockPaneScaffoldMotionScope.paneMotionDataList[1].targetPosition.x -
+ mockPaneScaffoldMotionScope.paneMotionDataList[1].targetSize.width
)
}
@Test
fun slideInFromRightOffset_noEnterFromRightPane_equalsZero() {
- mockPaneScaffoldMotionScope.paneMotions =
- listOf(EnterFromLeft, EnterFromLeft, EnterFromLeft)
+ mockPaneScaffoldMotionScope.updateMotions(EnterFromLeft, EnterFromLeft, EnterFromLeft)
assertThat(mockPaneScaffoldMotionScope.slideInFromRightOffset).isEqualTo(0)
}
@Test
fun slideInFromRightOffset_withEnterFromRightPane_useTheLeftestEdge() {
- mockPaneScaffoldMotionScope.paneMotions =
- listOf(EnterFromLeft, EnterFromRight, EnterFromRight)
+ mockPaneScaffoldMotionScope.updateMotions(EnterFromLeft, EnterFromRight, EnterFromRight)
assertThat(mockPaneScaffoldMotionScope.slideInFromRightOffset)
.isEqualTo(
mockPaneScaffoldMotionScope.scaffoldSize.width -
- mockPaneScaffoldMotionScope.targetPanePositions[1].x
+ mockPaneScaffoldMotionScope.paneMotionDataList[1].targetPosition.x
)
}
@Test
fun slideInFromRightOffset_withEnterFromRightDelayedPane_useTheLeftestEdge() {
- mockPaneScaffoldMotionScope.paneMotions =
- listOf(EnterFromLeft, EnterFromRightDelayed, EnterFromRight)
+ mockPaneScaffoldMotionScope.updateMotions(
+ EnterFromLeft,
+ EnterFromRightDelayed,
+ EnterFromRight
+ )
assertThat(mockPaneScaffoldMotionScope.slideInFromRightOffset)
.isEqualTo(
mockPaneScaffoldMotionScope.scaffoldSize.width -
- mockPaneScaffoldMotionScope.targetPanePositions[1].x
+ mockPaneScaffoldMotionScope.paneMotionDataList[1].targetPosition.x
)
}
@Test
fun slideOutToLeftOffset_noExitToLeftPane_equalsZero() {
- mockPaneScaffoldMotionScope.paneMotions =
- listOf(EnterFromRight, EnterFromRight, EnterFromRight)
+ mockPaneScaffoldMotionScope.updateMotions(EnterFromRight, EnterFromRight, EnterFromRight)
assertThat(mockPaneScaffoldMotionScope.slideOutToLeftOffset).isEqualTo(0)
}
@Test
fun slideOutToLeftOffset_withExitToLeftPane_useTheRightestEdge() {
- mockPaneScaffoldMotionScope.paneMotions = listOf(ExitToLeft, ExitToLeft, ExitToRight)
+ mockPaneScaffoldMotionScope.updateMotions(ExitToLeft, ExitToLeft, ExitToRight)
assertThat(mockPaneScaffoldMotionScope.slideOutToLeftOffset)
.isEqualTo(
- -mockPaneScaffoldMotionScope.currentPanePositions[1].x -
- mockPaneScaffoldMotionScope.currentPaneSizes[1].width
+ -mockPaneScaffoldMotionScope.paneMotionDataList[1].currentPosition.x -
+ mockPaneScaffoldMotionScope.paneMotionDataList[1].currentSize.width
)
}
@Test
fun slideOutToRightOffset_noExitToRightPane_equalsZero() {
- mockPaneScaffoldMotionScope.paneMotions =
- listOf(EnterFromRight, EnterFromRight, EnterFromRight)
+ mockPaneScaffoldMotionScope.updateMotions(EnterFromRight, EnterFromRight, EnterFromRight)
assertThat(mockPaneScaffoldMotionScope.slideOutToRightOffset).isEqualTo(0)
}
@Test
fun slideOutToRightOffset_withExitToRightPane_useTheLeftestEdge() {
- mockPaneScaffoldMotionScope.paneMotions = listOf(ExitToLeft, ExitToRight, ExitToRight)
+ mockPaneScaffoldMotionScope.updateMotions(ExitToLeft, ExitToRight, ExitToRight)
assertThat(mockPaneScaffoldMotionScope.slideOutToRightOffset)
.isEqualTo(
mockPaneScaffoldMotionScope.scaffoldSize.width -
- mockPaneScaffoldMotionScope.currentPanePositions[1].x
+ mockPaneScaffoldMotionScope.paneMotionDataList[1].currentPosition.x
)
}
}
@@ -308,25 +304,48 @@
@Suppress("PrimitiveInCollection") // No way to get underlying Long of IntSize or IntOffset
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
private val mockPaneScaffoldMotionScope =
- object : PaneScaffoldMotionScope {
- override val positionAnimationSpec: FiniteAnimationSpec<IntOffset> = tween()
- override val sizeAnimationSpec: FiniteAnimationSpec<IntSize> = spring()
- override val delayedPositionAnimationSpec: FiniteAnimationSpec<IntOffset> = snap()
- override val scaffoldSize: IntSize = IntSize(1000, 1000)
- override var currentPaneSizes: List<IntSize> =
- listOf(IntSize(1, 2), IntSize(3, 4), IntSize(5, 6))
- override var currentPanePositions: List<IntOffset> =
- listOf(IntOffset(3, 4), IntOffset(5, 6), IntOffset(7, 8))
- override var targetPaneSizes: List<IntSize> =
- listOf(IntSize(3, 4), IntSize(5, 6), IntSize(7, 8))
- override var targetPanePositions: List<IntOffset> =
- listOf(IntOffset(5, 6), IntOffset(7, 8), IntOffset(9, 0))
- override var paneMotions: List<PaneMotion> =
- listOf(ExitToLeft, EnterFromRight, EnterFromRight)
- override val motionProgress = 0.5F
+ ThreePaneScaffoldMotionScopeImpl().apply {
+ updateThreePaneMotion(
+ ThreePaneMotion(ExitToLeft, EnterFromRight, EnterFromRight),
+ MockThreePaneOrder
+ )
+ scaffoldSize = IntSize(1000, 1000)
+ paneMotionDataList[0].apply {
+ motion = ExitToLeft
+ currentSize = IntSize(1, 2)
+ currentPosition = IntOffset(3, 4)
+ targetSize = IntSize(3, 4)
+ targetPosition = IntOffset(5, 6)
+ }
+ paneMotionDataList[1].apply {
+ motion = ExitToLeft
+ currentSize = IntSize(3, 4)
+ currentPosition = IntOffset(5, 6)
+ targetSize = IntSize(5, 6)
+ targetPosition = IntOffset(7, 8)
+ }
+ paneMotionDataList[2].apply {
+ motion = ExitToLeft
+ currentSize = IntSize(5, 6)
+ currentPosition = IntOffset(7, 8)
+ targetSize = IntSize(7, 8)
+ targetPosition = IntOffset(9, 0)
+ }
}
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private fun ThreePaneScaffoldMotionScopeImpl.updateMotions(
+ primaryPaneMotion: PaneMotion,
+ secondaryPaneMotion: PaneMotion,
+ tertiaryPaneMotion: PaneMotion
+) {
+ updateThreePaneMotion(
+ ThreePaneMotion(primaryPaneMotion, secondaryPaneMotion, tertiaryPaneMotion),
+ MockThreePaneOrder
+ )
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
private val mockEnterFromLeftTransition =
slideInHorizontally(mockPaneScaffoldMotionScope.positionAnimationSpec) {
mockPaneScaffoldMotionScope.slideInFromLeftOffset
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt
index ea19285..2718a66 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt
@@ -32,6 +32,7 @@
@RunWith(JUnit4::class)
class PaneScaffoldDirectiveTest {
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateStandardPaneScaffoldDirective_compactWidth() {
val scaffoldDirective =
calculatePaneScaffoldDirective(
@@ -46,6 +47,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateStandardPaneScaffoldDirective_mediumWidth() {
val scaffoldDirective =
calculatePaneScaffoldDirective(
@@ -60,6 +62,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateStandardPaneScaffoldDirective_expandedWidth() {
val scaffoldDirective =
calculatePaneScaffoldDirective(
@@ -74,6 +77,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateStandardPaneScaffoldDirective_tabletop() {
val scaffoldDirective =
calculatePaneScaffoldDirective(
@@ -88,6 +92,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateDensePaneScaffoldDirective_compactWidth() {
val scaffoldDirective =
calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
@@ -102,6 +107,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateDensePaneScaffoldDirective_mediumWidth() {
val scaffoldDirective =
calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
@@ -116,6 +122,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateDensePaneScaffoldDirective_expandedWidth() {
val scaffoldDirective =
calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
@@ -130,6 +137,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateDensePaneScaffoldDirective_tabletop() {
val scaffoldDirective =
calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
@@ -144,6 +152,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateStandardPaneScaffoldDirective_alwaysAvoidHinge() {
val scaffoldDirective =
calculatePaneScaffoldDirective(
@@ -158,6 +167,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateStandardPaneScaffoldDirective_avoidOccludingHinge() {
val scaffoldDirective =
calculatePaneScaffoldDirective(
@@ -172,6 +182,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateStandardPaneScaffoldDirective_avoidSeparatingHinge() {
val scaffoldDirective =
calculatePaneScaffoldDirective(
@@ -186,6 +197,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateStandardPaneScaffoldDirective_neverAvoidHinge() {
val scaffoldDirective =
calculatePaneScaffoldDirective(
@@ -200,6 +212,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateDensePaneScaffoldDirective_alwaysAvoidHinge() {
val scaffoldDirective =
calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
@@ -214,6 +227,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateDensePaneScaffoldDirective_avoidOccludingHinge() {
val scaffoldDirective =
calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
@@ -228,6 +242,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateDensePaneScaffoldDirective_avoidSeparatingHinge() {
val scaffoldDirective =
calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
@@ -242,6 +257,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun test_calculateDensePaneScaffoldDirective_neverAvoidHinge() {
val scaffoldDirective =
calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotionTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotionTest.kt
deleted file mode 100644
index 39bfd7d..0000000
--- a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotionTest.kt
+++ /dev/null
@@ -1,369 +0,0 @@
-/*
- * Copyright 2024 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.adaptive.layout
-
-import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.VectorConverter
-import androidx.compose.animation.core.VectorizedAnimationSpec
-import androidx.compose.animation.core.spring
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@RunWith(JUnit4::class)
-class ThreePaneMotionTest {
- @Test
- fun noPane_noMotion() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(ThreePaneMotion.NoMotion)
- }
-
- @Test
- fun singlePane_firstToSecond_movesLeft() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(MovePanesToLeftMotion(SpacerSize))
- }
-
- @Test
- fun singlePane_firstToThird_movesLeft() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(MovePanesToLeftMotion(SpacerSize))
- }
-
- @Test
- fun singlePane_secondToThird_movesLeft() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(MovePanesToLeftMotion(SpacerSize))
- }
-
- @Test
- fun singlePane_secondToFirst_movesRight() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(MovePanesToRightMotion(SpacerSize))
- }
-
- @Test
- fun singlePane_thirdToFirst_movesRight() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(MovePanesToRightMotion(SpacerSize))
- }
-
- @Test
- fun singlePane_thirdToSecond_movesRight() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(MovePanesToRightMotion(SpacerSize))
- }
-
- @Test
- fun dualPane_hidesFirstShowsThird_movesLeft() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Expanded
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(MovePanesToLeftMotion(SpacerSize))
- }
-
- @Test
- fun dualPane_hidesThirdShowsFirst_movesRight() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Expanded
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(MovePanesToRightMotion(SpacerSize))
- }
-
- @Test
- fun dualPane_hidesSecondShowsThird_switchRightTwoPanes() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(SwitchRightTwoPanesMotion(SpacerSize))
- }
-
- @Test
- fun dualPane_hidesThirdShowsSecond_switchRightTwoPanes() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(SwitchRightTwoPanesMotion(SpacerSize))
- }
-
- @Test
- fun dualPane_hidesFirstShowsSecond_switchLeftTwoPanes() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Expanded
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(SwitchLeftTwoPanesMotion(SpacerSize))
- }
-
- @Test
- fun dualPane_hidesSecondShowsFirst_switchLeftTwoPanes() {
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Expanded
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(SwitchLeftTwoPanesMotion(SpacerSize))
- }
-
- @Test
- fun changeNumberOfPanes_noMotion() {
- // TODO(conradchen): Update this when we support motions in this case
- val motions =
- calculateThreePaneMotion(
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Expanded
- ),
- ThreePaneScaffoldValue(
- PaneAdaptedValue.Expanded,
- PaneAdaptedValue.Hidden,
- PaneAdaptedValue.Hidden
- ),
- PaneOrder,
- SpacerSize
- )
- assertThat(motions).isEqualTo(ThreePaneMotion.NoMotion)
- }
-
- @Test
- fun delayedSpring_identicalWithOriginPlusDelay() {
- val delayedRatio = 0.5f
-
- val originalSpec =
- spring(dampingRatio = 0.7f, stiffness = 500f, visibilityThreshold = 0.1f)
- .vectorize(Float.VectorConverter)
-
- val delayedSpec =
- DelayedSpringSpec(
- dampingRatio = 0.7f,
- stiffness = 500f,
- visibilityThreshold = 0.1f,
- delayedRatio = delayedRatio,
- )
- .vectorize(Float.VectorConverter)
-
- val originalDurationNanos = originalSpec.getDurationNanos()
- val delayedNanos = (originalDurationNanos * delayedRatio).toLong()
-
- fun assertValuesAt(playTimeNanos: Long) {
- assertValuesAreEqual(
- originalSpec.getValueFromNanos(playTimeNanos),
- delayedSpec.getValueFromNanos(playTimeNanos + delayedNanos)
- )
- }
-
- assertValuesAt(0)
- assertValuesAt((originalDurationNanos * 0.2).toLong())
- assertValuesAt((originalDurationNanos * 0.35).toLong())
- assertValuesAt((originalDurationNanos * 0.6).toLong())
- assertValuesAt((originalDurationNanos * 0.85).toLong())
- assertValuesAt(originalDurationNanos)
- }
-
- private fun VectorizedAnimationSpec<AnimationVector1D>.getDurationNanos(): Long =
- getDurationNanos(InitialValue, TargetValue, InitialVelocity)
-
- private fun VectorizedAnimationSpec<AnimationVector1D>.getValueFromNanos(
- playTimeNanos: Long
- ): Float = getValueFromNanos(playTimeNanos, InitialValue, TargetValue, InitialVelocity).value
-
- private fun assertValuesAreEqual(value1: Float, value2: Float) {
- assertThat(value1 - value2).isWithin(Tolerance)
- }
-
- companion object {
- private val InitialValue = AnimationVector1D(0f)
- private val TargetValue = AnimationVector1D(1f)
- private val InitialVelocity = AnimationVector1D(0f)
- private const val Tolerance = 0.001f
- }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal val PaneOrder = SupportingPaneScaffoldDefaults.PaneOrder
-internal const val SpacerSize = 123
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt
index 82e8e71..fa35e39 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt
@@ -42,24 +42,22 @@
lookaheadScope: LookaheadScope,
enabled: Boolean
) =
- if (enabled) {
- this.then(
- AnimateBoundsElement(
- animateFraction,
- sizeAnimationSpec,
- positionAnimationSpec,
- lookaheadScope
- )
+ this.then(
+ AnimateBoundsElement(
+ animateFraction,
+ sizeAnimationSpec,
+ positionAnimationSpec,
+ lookaheadScope,
+ enabled,
)
- } else {
- this
- }
+ )
private data class AnimateBoundsElement(
private val animateFraction: () -> Float,
private val sizeAnimationSpec: FiniteAnimationSpec<IntSize>,
private val positionAnimationSpec: FiniteAnimationSpec<IntOffset>,
- private val lookaheadScope: LookaheadScope
+ private val lookaheadScope: LookaheadScope,
+ private val enabled: Boolean
) : ModifierNodeElement<AnimateBoundsNode>() {
private val inspectorInfo = debugInspectorInfo {
name = "animateBounds"
@@ -67,6 +65,7 @@
properties["sizeAnimationSpec"] = sizeAnimationSpec
properties["positionAnimationSpec"] = positionAnimationSpec
properties["lookaheadScope"] = lookaheadScope
+ properties["enabled"] = enabled
}
override fun create(): AnimateBoundsNode {
@@ -75,6 +74,7 @@
sizeAnimationSpec,
positionAnimationSpec,
lookaheadScope,
+ enabled,
)
}
@@ -83,6 +83,7 @@
node.sizeAnimationSpec = sizeAnimationSpec
node.positionAnimationSpec = positionAnimationSpec
node.lookaheadScope = lookaheadScope
+ node.enabled = enabled
}
override fun InspectorInfo.inspectableProperties() {
@@ -95,6 +96,7 @@
sizeAnimationSpec: FiniteAnimationSpec<IntSize>,
positionAnimationSpec: FiniteAnimationSpec<IntOffset>,
var lookaheadScope: LookaheadScope,
+ var enabled: Boolean
) : ApproachLayoutModifierNode, Modifier.Node() {
val sizeTracker = SizeTracker(sizeAnimationSpec)
val positionTracker = PositionTracker(positionAnimationSpec)
@@ -111,30 +113,33 @@
}
get() = positionTracker.animationSpec
- override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean =
- animateFraction() != 1f
+ // If animateBounds is not enabled, we need to do approach measure at least once so the size
+ // tracker and the position tracker will be kept updated.
+ override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
+ sizeTracker.updateTargetSize(lookaheadSize)
+ return enabled && animateFraction() != 1f
+ }
+ // If animateBounds is not enabled, we need to do approach measure at least once so the size
+ // tracker and the position tracker will be kept updated.
override fun Placeable.PlacementScope.isPlacementApproachInProgress(
lookaheadCoordinates: LayoutCoordinates
- ) = animateFraction() != 1f
+ ): Boolean {
+ positionTracker.updateTargetOffset(lookaheadOffset(lookaheadScope))
+ return enabled && animateFraction() != 1f
+ }
override fun ApproachMeasureScope.approachMeasure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
- // When layout changes, the lookahead pass will calculate a new final size for the
- // child modifier. This lookahead size can be used to animate the size
- // change, such that the animation starts from the current size and gradually
- // change towards `lookaheadSize`.
- sizeTracker.updateTargetSize(lookaheadSize)
+ // Use the current animating fraction to get the approach size and offset of the current
+ // animating layut toward the target size and offset updated in measure().
val (width, height) = sizeTracker.updateAndGetCurrentSize(animateFraction())
- // Creates a fixed set of constraints using the animated size
val animatedConstraints = Constraints.fixed(width, height)
- // Measure child/children with animated constraints.
val placeable = measurable.measure(animatedConstraints)
return layout(placeable.width, placeable.height) {
coordinates?.let {
- positionTracker.updateTargetOffset(lookaheadOffset(lookaheadScope))
placeable.place(
convertOffsetToLookaheadCoordinates(
positionTracker.updateAndGetCurrentOffset(animateFraction()),
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
index edaad4f..815399d 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
@@ -47,12 +47,12 @@
* @param extraPane the extra pane of the scaffold, which is supposed to hold any supplementary info
* besides the list and the detail panes, for example, a task list or a mini-calendar view of a
* mail app. See [ListDetailPaneScaffoldRole.Extra].
- * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
- * change pane expansion state. Note that by default this argument will be `null`, and there won't
- * be a drag handle rendered and users won't be able to drag to change the pane split. You can
- * provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if
- * there's no drag handle, you can still modify [paneExpansionState] directly to apply pane
- * expansion.
+ * @param paneMotions The specified motion of the panes. By default the value will be calculated by
+ * [calculateListDetailPaneScaffoldMotion] according to the target [ThreePaneScaffoldValue].
+ * @param paneExpansionDragHandle provide a custom pane expansion drag handle to allow users to
+ * resize panes and change the pane expansion state by dragging. This is `null` by default, which
+ * renders no drag handle. Even there's no drag handle, you can still change pane size directly
+ * via modifying [paneExpansionState].
* @param paneExpansionState the state object of pane expansion.
*/
@ExperimentalMaterial3AdaptiveApi
@@ -60,10 +60,11 @@
fun ListDetailPaneScaffold(
directive: PaneScaffoldDirective,
value: ThreePaneScaffoldValue,
- listPane: @Composable ThreePaneScaffoldScope.() -> Unit,
- detailPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+ listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
+ detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
modifier: Modifier = Modifier,
- extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+ extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
+ paneMotions: ThreePaneMotion = calculateListDetailPaneScaffoldMotion(value),
paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
null,
paneExpansionState: PaneExpansionState = rememberPaneExpansionState(value),
@@ -73,6 +74,7 @@
scaffoldDirective = directive,
scaffoldValue = value,
paneOrder = ListDetailPaneScaffoldDefaults.PaneOrder,
+ paneMotions = paneMotions,
secondaryPane = listPane,
tertiaryPane = extraPane,
paneExpansionDragHandle = paneExpansionDragHandle,
@@ -102,24 +104,38 @@
* @param extraPane the extra pane of the scaffold, which is supposed to hold any supplementary info
* besides the list and the detail panes, for example, a task list or a mini-calendar view of a
* mail app. See [ListDetailPaneScaffoldRole.Extra].
+ * @param paneMotions The specified motion of the panes. By default the value will be calculated by
+ * [calculateListDetailPaneScaffoldMotion] according to the target [ThreePaneScaffoldValue].
+ * @param paneExpansionDragHandle provide a custom pane expansion drag handle to allow users to
+ * resize panes and change the pane expansion state by dragging. This is `null` by default, which
+ * renders no drag handle. Even there's no drag handle, you can still change pane size directly
+ * via modifying [paneExpansionState].
+ * @param paneExpansionState the state object of pane expansion.
*/
@ExperimentalMaterial3AdaptiveApi
@Composable
fun ListDetailPaneScaffold(
directive: PaneScaffoldDirective,
scaffoldState: ThreePaneScaffoldState,
- listPane: @Composable ThreePaneScaffoldScope.() -> Unit,
- detailPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+ listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
+ detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
modifier: Modifier = Modifier,
- extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+ extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
+ paneMotions: ThreePaneMotion = scaffoldState.calculateListDetailPaneScaffoldMotion(),
+ paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
+ null,
+ paneExpansionState: PaneExpansionState = rememberPaneExpansionState(scaffoldState.targetState),
) {
ThreePaneScaffold(
modifier = modifier.fillMaxSize(),
scaffoldDirective = directive,
scaffoldState = scaffoldState,
paneOrder = ListDetailPaneScaffoldDefaults.PaneOrder,
+ paneMotions = paneMotions,
secondaryPane = listPane,
tertiaryPane = extraPane,
+ paneExpansionDragHandle = paneExpansionDragHandle,
+ paneExpansionState = paneExpansionState,
primaryPane = detailPane
)
}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
index ea17ef7..df65278 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
@@ -39,29 +39,27 @@
*/
@ExperimentalMaterial3AdaptiveApi
@Composable
-fun ThreePaneScaffoldScope.AnimatedPane(
+fun <S, T : PaneScaffoldValue<S>> ExtendedPaneScaffoldPaneScope<S, T>.AnimatedPane(
modifier: Modifier = Modifier,
content: (@Composable AnimatedPaneScope.() -> Unit),
) {
- val keepShowing =
- scaffoldStateTransition.currentState[role] != PaneAdaptedValue.Hidden &&
- scaffoldStateTransition.targetState[role] != PaneAdaptedValue.Hidden
- val animateFraction = { scaffoldStateTransitionFraction }
+ val animatingBounds = paneMotion == DefaultPaneMotion.AnimateBounds
+ val motionProgress = { motionProgress }
scaffoldStateTransition.AnimatedVisibility(
- visible = { value: ThreePaneScaffoldValue -> value[role] != PaneAdaptedValue.Hidden },
+ visible = { value: T -> value[paneRole] != PaneAdaptedValue.Hidden },
modifier =
modifier
.animatedPane()
.animateBounds(
- animateFraction = animateFraction,
- positionAnimationSpec = positionAnimationSpec,
- sizeAnimationSpec = sizeAnimationSpec,
- lookaheadScope = this,
- enabled = keepShowing
+ motionProgress,
+ sizeAnimationSpec,
+ positionAnimationSpec,
+ this,
+ animatingBounds
)
- .then(if (keepShowing) Modifier else Modifier.clipToBounds()),
- enter = enterTransition,
- exit = exitTransition
+ .then(if (animatingBounds) Modifier else Modifier.clipToBounds()),
+ enter = with(paneMotion) { enterTransition },
+ exit = with(paneMotion) { exitTransition }
) {
AnimatedPaneScopeImpl(this).content()
}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt
index 6b4517f..0409b5c 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt
@@ -43,7 +43,7 @@
color: Color,
modifier: Modifier = Modifier,
) {
- val animationProgress = { scaffoldStateTransitionFraction }
+ val animationProgress = { motionProgress }
Box(
modifier =
modifier
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt
index 597862e..58798db 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt
@@ -16,7 +16,6 @@
package androidx.compose.material3.adaptive.layout
-import androidx.annotation.VisibleForTesting
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.FiniteAnimationSpec
@@ -28,35 +27,102 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachReversed
import kotlin.math.max
import kotlin.math.min
+/**
+ * Scope for performing pane motions within a pane scaffold. It provides the spec and necessary info
+ * to decide a pane's [EnterTransition] and [ExitTransition], as well as how bounds morphing will be
+ * performed.
+ */
@Suppress("PrimitiveInCollection") // No way to get underlying Long of IntSize or IntOffset
@ExperimentalMaterial3AdaptiveApi
-internal interface PaneScaffoldMotionScope {
+sealed interface PaneScaffoldMotionScope {
+ /**
+ * The position animation spec of the associated pane to the scope. [AnimatedPane] will use this
+ * value to perform pane animations during scaffold state changes.
+ */
val positionAnimationSpec: FiniteAnimationSpec<IntOffset>
+
+ /**
+ * The size animation spec of the associated pane to the scope. [AnimatedPane] will use this
+ * value to perform pane animations during scaffold state changes.
+ */
val sizeAnimationSpec: FiniteAnimationSpec<IntSize>
+
+ /**
+ * The delayed position animation spec of the associated pane to the scope. [AnimatedPane] will
+ * use this value to perform pane position animations during scaffold state changes when an
+ * animation needs to be played with a delay.
+ */
val delayedPositionAnimationSpec: FiniteAnimationSpec<IntOffset>
+
+ /**
+ * The scaffold's current size. Note that the value of the field will only be updated during
+ * measurement of the scaffold and before the first measurement the value will be
+ * [IntSize.Zero].
+ *
+ * Note that this field is not backed by snapshot states so it's supposed to be only read
+ * proactively by the motion logic "on-the-fly" when the scaffold motion is happening.
+ */
val scaffoldSize: IntSize
- val currentPaneSizes: List<IntSize>
- val currentPanePositions: List<IntOffset>
- val targetPaneSizes: List<IntSize>
- val targetPanePositions: List<IntOffset>
- val paneMotions: List<PaneMotion>
- val motionProgress: Float
+
+ /**
+ * [PaneMotionData] of all panes in the scaffold corresponding to the scaffold's current state
+ * transition and motion settings, listed in panes' horizontal order.
+ *
+ * The size of position values of [PaneMotionData] in the list will only be update during
+ * measurement of the scaffold and before the first measurement their values will be
+ * [IntSize.Zero] or [IntOffset.Zero].
+ *
+ * Note that the aforementioned fields are not backed by snapshot states so they are supposed to
+ * be only read proactively by the motion logic "on-the-fly" when the scaffold motion is
+ * happening.
+ */
+ val paneMotionDataList: List<PaneMotionData>
+}
+
+/**
+ * A class to collect motion-relevant data of a specific pane.
+ *
+ * @property motion The specified [PaneMotion] of the pane.
+ * @property currentSize The current measured size of the pane that it should animate from.
+ * @property currentPosition The current placement of the pane that it should animate from, with the
+ * offset relative to the associated pane scaffold's local coordinates.
+ * @property targetSize The target measured size of the pane that it should animate to.
+ * @property targetPosition The target placement of the pane that it should animate to, with the
+ * offset relative to the associated pane scaffold's local coordinates.
+ */
+@ExperimentalMaterial3AdaptiveApi
+class PaneMotionData internal constructor() {
+ var motion: PaneMotion = DefaultPaneMotion.NoMotion
+ internal set
+
+ var currentSize: IntSize = IntSize.Zero
+ internal set
+
+ var currentPosition: IntOffset = IntOffset.Zero
+ internal set
+
+ var targetSize: IntSize = IntSize.Zero
+ internal set
+
+ var targetPosition: IntOffset = IntOffset.Zero
+ internal set
}
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
internal val PaneScaffoldMotionScope.slideInFromLeftOffset: Int
get() {
// Find the right edge offset of the rightmost pane that enters from its left
- for (i in paneMotions.lastIndex downTo 0) {
+ paneMotionDataList.fastForEachReversed {
if (
- paneMotions[i] == DefaultPaneMotion.EnterFromLeft ||
- paneMotions[i] == DefaultPaneMotion.EnterFromLeftDelayed
+ it.motion == DefaultPaneMotion.EnterFromLeft ||
+ it.motion == DefaultPaneMotion.EnterFromLeftDelayed
) {
- return -targetPanePositions[i].x - targetPaneSizes[i].width
+ return -it.targetPosition.x - it.targetSize.width
}
}
return 0
@@ -66,12 +132,12 @@
internal val PaneScaffoldMotionScope.slideInFromRightOffset: Int
get() {
// Find the left edge offset of the leftmost pane that enters from its right
- paneMotions.fastForEachIndexed { i, paneMotion ->
+ paneMotionDataList.fastForEach {
if (
- paneMotion == DefaultPaneMotion.EnterFromRight ||
- paneMotion == DefaultPaneMotion.EnterFromRightDelayed
+ it.motion == DefaultPaneMotion.EnterFromRight ||
+ it.motion == DefaultPaneMotion.EnterFromRightDelayed
) {
- return scaffoldSize.width - targetPanePositions[i].x
+ return scaffoldSize.width - it.targetPosition.x
}
}
return 0
@@ -81,9 +147,9 @@
internal val PaneScaffoldMotionScope.slideOutToLeftOffset: Int
get() {
// Find the right edge offset of the rightmost pane that exits to its left
- for (i in paneMotions.lastIndex downTo 0) {
- if (paneMotions[i] == DefaultPaneMotion.ExitToLeft) {
- return -currentPanePositions[i].x - currentPaneSizes[i].width
+ paneMotionDataList.fastForEachReversed {
+ if (it.motion == DefaultPaneMotion.ExitToLeft) {
+ return -it.currentPosition.x - it.currentSize.width
}
}
return 0
@@ -93,17 +159,21 @@
internal val PaneScaffoldMotionScope.slideOutToRightOffset: Int
get() {
// Find the left edge offset of the leftmost pane that exits to its right
- paneMotions.fastForEachIndexed { i, paneMotion ->
- if (paneMotion == DefaultPaneMotion.ExitToRight) {
- return scaffoldSize.width - currentPanePositions[i].x
+ paneMotionDataList.fastForEach {
+ if (it.motion == DefaultPaneMotion.ExitToRight) {
+ return scaffoldSize.width - it.currentPosition.x
}
}
return 0
}
+/** Interface to specify a custom pane enter/exit motion when a pane's visibility changes. */
@ExperimentalMaterial3AdaptiveApi
-internal interface PaneMotion {
+interface PaneMotion {
+ /** The [EnterTransition] of a pane under the given [PaneScaffoldMotionScope] */
val PaneScaffoldMotionScope.enterTransition: EnterTransition
+
+ /** The [ExitTransition] of a pane under the given [PaneScaffoldMotionScope] */
val PaneScaffoldMotionScope.exitTransition: ExitTransition
}
@@ -168,7 +238,6 @@
}
@ExperimentalMaterial3AdaptiveApi
-@VisibleForTesting
internal fun <T> calculatePaneMotion(
previousScaffoldValue: PaneScaffoldValue<T>,
currentScaffoldValue: PaneScaffoldValue<T>,
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
index 04b653e..3fd930e 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
@@ -16,7 +16,10 @@
package androidx.compose.material3.adaptive.layout
+import androidx.compose.animation.core.Transition
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ParentDataModifierNode
import androidx.compose.ui.platform.InspectorInfo
@@ -25,7 +28,48 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-/** Scope for the panes of pane scaffolds. */
+/**
+ * Extended scope for the panes of pane scaffolds. All pane scaffolds will implement this interface
+ * to provide necessary info for panes to correctly render their content, motion, etc.
+ *
+ * @param Role The type of roles that denotes panes in the associated pane scaffold.
+ * @param ScaffoldValue The type of scaffold values that denotes the [PaneAdaptedValue]s in the
+ * associated pane scaffold.
+ * @see ThreePaneScaffoldPaneScope
+ * @see PaneScaffoldScope
+ * @see PaneScaffoldMotionScope
+ * @see PaneScaffoldTransitionScope
+ * @see PaneScaffoldPaneScope
+ * @see LookaheadScope
+ */
+@ExperimentalMaterial3AdaptiveApi
+sealed interface ExtendedPaneScaffoldPaneScope<Role, ScaffoldValue : PaneScaffoldValue<Role>> :
+ ExtendedPaneScaffoldScope<Role, ScaffoldValue>, PaneScaffoldPaneScope<Role>
+
+/**
+ * Extended scope for pane scaffolds. All pane scaffolds will implement this interface to provide
+ * necessary info for its sub-composables to correctly render their content, motion, etc.
+ *
+ * @param Role The type of roles that denotes panes in the associated pane scaffold.
+ * @param ScaffoldValue The type of scaffold values that denotes the [PaneAdaptedValue]s in the
+ * associated pane scaffold.
+ * @see ThreePaneScaffoldScope
+ * @see PaneScaffoldScope
+ * @see PaneScaffoldMotionScope
+ * @see PaneScaffoldTransitionScope
+ * @see LookaheadScope
+ */
+@ExperimentalMaterial3AdaptiveApi
+sealed interface ExtendedPaneScaffoldScope<Role, ScaffoldValue : PaneScaffoldValue<Role>> :
+ PaneScaffoldScope,
+ PaneScaffoldMotionScope,
+ PaneScaffoldTransitionScope<Role, ScaffoldValue>,
+ LookaheadScope
+
+/**
+ * The base scope of pane scaffolds, which provides scoped functions that supported by pane
+ * scaffolds.
+ */
sealed interface PaneScaffoldScope {
/**
* This modifier specifies the preferred width for a pane, and the pane scaffold implementation
@@ -39,6 +83,32 @@
fun Modifier.preferredWidth(width: Dp): Modifier
}
+/**
+ * The transition scope of pane scaffold implementations, which provides the current transition info
+ * of the associated pane scaffold.
+ */
+@ExperimentalMaterial3AdaptiveApi
+sealed interface PaneScaffoldTransitionScope<Role, ScaffoldValue : PaneScaffoldValue<Role>> {
+ /** The current scaffold state transition between [PaneScaffoldValue]s. */
+ val scaffoldStateTransition: Transition<ScaffoldValue>
+
+ /** The current motion progress. */
+ val motionProgress: Float
+}
+
+/**
+ * The pane scope of the current pane under the scope, which provides the pane relevant info like
+ * its role and [PaneMotion].
+ */
+@ExperimentalMaterial3AdaptiveApi
+sealed interface PaneScaffoldPaneScope<Role> {
+ /** The role of the current pane in the scope. */
+ val paneRole: Role
+
+ /** The specified pane motion of the current pane in the scope. */
+ val paneMotion: PaneMotion
+}
+
internal abstract class PaneScaffoldScopeImpl : PaneScaffoldScope {
override fun Modifier.preferredWidth(width: Dp): Modifier {
require(width == Dp.Unspecified || width > 0.dp) { "invalid width" }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
index d0af5ed..a2a06c7 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("DEPRECATED") // Deprecated import WindowWidthSizeClass.
+
package androidx.compose.material3.adaptive.layout
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
@@ -27,7 +29,6 @@
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.window.core.layout.WindowWidthSizeClass
/**
* Calculates the recommended [PaneScaffoldDirective] from a given [WindowAdaptiveInfo]. Use this
@@ -44,6 +45,7 @@
* @return an [PaneScaffoldDirective] to be used to decide adaptive layout states.
*/
@ExperimentalMaterial3AdaptiveApi
+@Suppress("DEPRECATION") // WindowWidthSizeClass is deprecated
fun calculatePaneScaffoldDirective(
windowAdaptiveInfo: WindowAdaptiveInfo,
verticalHingePolicy: HingePolicy = HingePolicy.AvoidSeparating
@@ -51,11 +53,11 @@
val maxHorizontalPartitions: Int
val horizontalPartitionSpacerSize: Dp
when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
- WindowWidthSizeClass.COMPACT -> {
+ androidx.window.core.layout.WindowWidthSizeClass.COMPACT -> {
maxHorizontalPartitions = 1
horizontalPartitionSpacerSize = 0.dp
}
- WindowWidthSizeClass.MEDIUM -> {
+ androidx.window.core.layout.WindowWidthSizeClass.MEDIUM -> {
maxHorizontalPartitions = 1
horizontalPartitionSpacerSize = 0.dp
}
@@ -108,12 +110,14 @@
* @return an [PaneScaffoldDirective] to be used to decide adaptive layout states.
*/
@ExperimentalMaterial3AdaptiveApi
+@Suppress("DEPRECATION") // WindowWidthSizeClass is deprecated
fun calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
windowAdaptiveInfo: WindowAdaptiveInfo,
verticalHingePolicy: HingePolicy = HingePolicy.AvoidSeparating
): PaneScaffoldDirective {
val isMediumWidth =
- windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.MEDIUM
+ windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass ==
+ androidx.window.core.layout.WindowWidthSizeClass.MEDIUM
return with(calculatePaneScaffoldDirective(windowAdaptiveInfo, verticalHingePolicy)) {
copy(
maxHorizontalPartitions = if (isMediumWidth) 2 else maxHorizontalPartitions,
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldValue.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldValue.kt
index d3b1d1f..314207b 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldValue.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldValue.kt
@@ -18,7 +18,13 @@
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+/**
+ * Interface to provide adapted value of panes.
+ *
+ * @see ThreePaneScaffoldValue
+ */
@ExperimentalMaterial3AdaptiveApi
-internal interface PaneScaffoldValue<T> {
+sealed interface PaneScaffoldValue<T> {
+ /** Returns the [PaneAdaptedValue] of the given [role] of a pane. */
operator fun get(role: T): PaneAdaptedValue
}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
index 14f5f0e..f828c05 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
@@ -41,12 +41,12 @@
* @param extraPane the extra pane of the scaffold, which is supposed to hold any additional content
* besides the main and the supporting panes, for example, a styling panel in a doc app. See
* [SupportingPaneScaffoldRole.Extra].
- * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
- * change pane expansion state. Note that by default this argument will be `null`, and there won't
- * be a drag handle rendered and users won't be able to drag to change the pane split. You can
- * provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if
- * there's no drag handle, you can still modify [paneExpansionState] directly to apply pane
- * expansion.
+ * @param paneMotions The specified motion of the panes. By default the value will be calculated by
+ * [calculateSupportingPaneScaffoldMotion] according to the target [ThreePaneScaffoldValue].
+ * @param paneExpansionDragHandle provide a custom pane expansion drag handle to allow users to
+ * resize panes and change the pane expansion state by dragging. This is `null` by default, which
+ * renders no drag handle. Even there's no drag handle, you can still change pane size directly
+ * via modifying [paneExpansionState].
* @param paneExpansionState the state object of pane expansion.
*/
@ExperimentalMaterial3AdaptiveApi
@@ -54,10 +54,11 @@
fun SupportingPaneScaffold(
directive: PaneScaffoldDirective,
value: ThreePaneScaffoldValue,
- mainPane: @Composable ThreePaneScaffoldScope.() -> Unit,
- supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+ mainPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
+ supportingPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
modifier: Modifier = Modifier,
- extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+ extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
+ paneMotions: ThreePaneMotion = calculateSupportingPaneScaffoldMotion(value),
paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
null,
paneExpansionState: PaneExpansionState = rememberPaneExpansionState(value),
@@ -69,6 +70,7 @@
paneOrder = SupportingPaneScaffoldDefaults.PaneOrder,
secondaryPane = supportingPane,
tertiaryPane = extraPane,
+ paneMotions = paneMotions,
paneExpansionDragHandle = paneExpansionDragHandle,
paneExpansionState = paneExpansionState,
primaryPane = mainPane
@@ -95,16 +97,27 @@
* @param extraPane the extra pane of the scaffold, which is supposed to hold any additional content
* besides the main and the supporting panes, for example, a styling panel in a doc app. See
* [SupportingPaneScaffoldRole.Extra].
+ * @param paneMotions The specified motion of the panes. By default the value will be calculated by
+ * [calculateSupportingPaneScaffoldMotion] according to the target [ThreePaneScaffoldValue].
+ * @param paneExpansionDragHandle provide a custom pane expansion drag handle to allow users to
+ * resize panes and change the pane expansion state by dragging. This is `null` by default, which
+ * renders no drag handle. Even there's no drag handle, you can still change pane size directly
+ * via modifying [paneExpansionState].
+ * @param paneExpansionState the state object of pane expansion.
*/
@ExperimentalMaterial3AdaptiveApi
@Composable
fun SupportingPaneScaffold(
directive: PaneScaffoldDirective,
scaffoldState: ThreePaneScaffoldState,
- mainPane: @Composable ThreePaneScaffoldScope.() -> Unit,
- supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+ mainPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
+ supportingPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
modifier: Modifier = Modifier,
- extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+ extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
+ paneMotions: ThreePaneMotion = scaffoldState.calculateSupportingPaneScaffoldMotion(),
+ paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
+ null,
+ paneExpansionState: PaneExpansionState = rememberPaneExpansionState(scaffoldState.targetState),
) {
ThreePaneScaffold(
modifier = modifier.fillMaxSize(),
@@ -113,6 +126,9 @@
paneOrder = SupportingPaneScaffoldDefaults.PaneOrder,
secondaryPane = supportingPane,
tertiaryPane = extraPane,
+ paneMotions = paneMotions,
+ paneExpansionDragHandle = paneExpansionDragHandle,
+ paneExpansionState = paneExpansionState,
primaryPane = mainPane
)
}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt
index 28ecdc3..23a9c59 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt
@@ -16,305 +16,259 @@
package androidx.compose.material3.adaptive.layout
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.AnimationVector
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.TwoWayConverter
import androidx.compose.animation.core.VectorizedFiniteAnimationSpec
import androidx.compose.animation.core.VisibilityThreshold
-import androidx.compose.animation.core.snap
import androidx.compose.animation.core.spring
-import androidx.compose.animation.slideInHorizontally
-import androidx.compose.animation.slideOutHorizontally
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastForEachIndexed
-/** Holds the transitions that can be applied to the different panes. */
+/**
+ * Calculates the default [ThreePaneMotion] of [ListDetailPaneScaffold] according to the given
+ * [ThreePaneScaffoldState]'s current and target values.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun ThreePaneScaffoldState.calculateListDetailPaneScaffoldMotion(): ThreePaneMotion =
+ calculateThreePaneMotion(ListDetailPaneScaffoldDefaults.PaneOrder)
+
+/**
+ * Calculates the default [ThreePaneMotion] of [ListDetailPaneScaffold] according to the target and
+ * the previously remembered [ThreePaneScaffoldValue].
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun calculateListDetailPaneScaffoldMotion(
+ targetScaffoldValue: ThreePaneScaffoldValue
+): ThreePaneMotion =
+ calculateThreePaneMotion(targetScaffoldValue, ListDetailPaneScaffoldDefaults.PaneOrder)
+
+/**
+ * Calculates the default [ThreePaneMotion] of [SupportingPaneScaffold] according to the given
+ * [ThreePaneScaffoldState]'s current and target values.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun ThreePaneScaffoldState.calculateSupportingPaneScaffoldMotion(): ThreePaneMotion =
+ calculateThreePaneMotion(SupportingPaneScaffoldDefaults.PaneOrder)
+
+/**
+ * Calculates the default [ThreePaneMotion] of [SupportingPaneScaffold] according to the target and
+ * the previously remembered [ThreePaneScaffoldValue].
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun calculateSupportingPaneScaffoldMotion(
+ targetScaffoldValue: ThreePaneScaffoldValue
+): ThreePaneMotion =
+ calculateThreePaneMotion(targetScaffoldValue, SupportingPaneScaffoldDefaults.PaneOrder)
+
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+internal fun ThreePaneScaffoldState.calculateThreePaneMotion(
+ paneOrder: ThreePaneScaffoldHorizontalOrder
+): ThreePaneMotion {
+ class ThreePaneMotionHolder(var value: ThreePaneMotion)
+
+ val resultHolder = remember { ThreePaneMotionHolder(ThreePaneMotion.NoMotion) }
+ if (currentState != targetState) {
+ // Only update motions when the state changes to prevent unnecessary recomposition at the
+ // end of state transitions.
+ val ltrPaneOrder = paneOrder.toLtrOrder(LocalLayoutDirection.current)
+ val paneMotions = calculatePaneMotion(currentState, targetState, ltrPaneOrder)
+ resultHolder.value =
+ ThreePaneMotion(
+ paneMotions[ltrPaneOrder.indexOf(ThreePaneScaffoldRole.Primary)],
+ paneMotions[ltrPaneOrder.indexOf(ThreePaneScaffoldRole.Secondary)],
+ paneMotions[ltrPaneOrder.indexOf(ThreePaneScaffoldRole.Tertiary)]
+ )
+ }
+ return resultHolder.value
+}
+
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+internal fun calculateThreePaneMotion(
+ targetScaffoldValue: ThreePaneScaffoldValue,
+ paneOrder: ThreePaneScaffoldHorizontalOrder
+): ThreePaneMotion {
+ class ThreePaneScaffoldValueHolder(var value: ThreePaneScaffoldValue)
+
+ val layoutDirection = LocalLayoutDirection.current
+ val ltrPaneOrder =
+ remember(paneOrder, layoutDirection) { paneOrder.toLtrOrder(layoutDirection) }
+ val previousScaffoldValue = remember { ThreePaneScaffoldValueHolder(targetScaffoldValue) }
+ val threePaneMotion =
+ remember(targetScaffoldValue, ltrPaneOrder) {
+ val previousValue = previousScaffoldValue.value
+ previousScaffoldValue.value = targetScaffoldValue
+ val paneMotions = calculatePaneMotion(previousValue, targetScaffoldValue, ltrPaneOrder)
+ ThreePaneMotion(
+ paneMotions[ltrPaneOrder.indexOf(ThreePaneScaffoldRole.Primary)],
+ paneMotions[ltrPaneOrder.indexOf(ThreePaneScaffoldRole.Secondary)],
+ paneMotions[ltrPaneOrder.indexOf(ThreePaneScaffoldRole.Tertiary)]
+ )
+ }
+ return threePaneMotion
+}
+
+/**
+ * The class that provides motion settings for three pane scaffolds like [ListDetailPaneScaffold]
+ * and [SupportingPaneScaffold].
+ *
+ * @param primaryPaneMotion the specified [PaneMotion] of the primary pane, i.e.,
+ * [ListDetailPaneScaffoldRole.Detail] or [SupportingPaneScaffoldRole.Main].
+ * @param secondaryPaneMotion the specified [PaneMotion] of the secondary pane, i.e.,
+ * [ListDetailPaneScaffoldRole.List] or [SupportingPaneScaffoldRole.Supporting].
+ * @param tertiaryPaneMotion the specified [PaneMotion] of the tertiary pane, i.e.,
+ * [ListDetailPaneScaffoldRole.Extra] or [SupportingPaneScaffoldRole.Extra].
+ * @param sizeAnimationSpec the specified [FiniteAnimationSpec] when animating pane size changes.
+ * @param positionAnimationSpec the specified [FiniteAnimationSpec] when animating pane position
+ * changes.
+ * @param delayedPositionAnimationSpec the specified [FiniteAnimationSpec] when animating pane
+ * position changes with a delay to emphasize entering panes.
+ */
@ExperimentalMaterial3AdaptiveApi
@Immutable
-internal open class ThreePaneMotion
-internal constructor(
- internal val positionAnimationSpec: FiniteAnimationSpec<IntOffset> = snap(),
- internal val sizeAnimationSpec: FiniteAnimationSpec<IntSize> = snap(),
- private val firstPaneEnterTransition: EnterTransition = EnterTransition.None,
- private val firstPaneExitTransition: ExitTransition = ExitTransition.None,
- private val secondPaneEnterTransition: EnterTransition = EnterTransition.None,
- private val secondPaneExitTransition: ExitTransition = ExitTransition.None,
- private val thirdPaneEnterTransition: EnterTransition = EnterTransition.None,
- private val thirdPaneExitTransition: ExitTransition = ExitTransition.None
+class ThreePaneMotion(
+ internal val primaryPaneMotion: PaneMotion,
+ internal val secondaryPaneMotion: PaneMotion,
+ internal val tertiaryPaneMotion: PaneMotion,
+ val sizeAnimationSpec: FiniteAnimationSpec<IntSize> =
+ ThreePaneMotionDefaults.PaneSizeAnimationSpec,
+ val positionAnimationSpec: FiniteAnimationSpec<IntOffset> =
+ ThreePaneMotionDefaults.PanePositionAnimationSpec,
+ val delayedPositionAnimationSpec: FiniteAnimationSpec<IntOffset> =
+ ThreePaneMotionDefaults.PanePositionAnimationSpecDelayed
) {
+ /**
+ * Makes a copy of [ThreePaneMotion] with override values.
+ *
+ * @param primaryPaneMotion the specified [PaneMotion] of the primary pane, i.e.,
+ * [ListDetailPaneScaffoldRole.Detail] or [SupportingPaneScaffoldRole.Main].
+ * @param secondaryPaneMotion the specified [PaneMotion] of the secondary pane, i.e.,
+ * [ListDetailPaneScaffoldRole.List] or [SupportingPaneScaffoldRole.Supporting].
+ * @param tertiaryPaneMotion the specified [PaneMotion] of the tertiary pane, i.e.,
+ * [ListDetailPaneScaffoldRole.Extra] or [SupportingPaneScaffoldRole.Extra].
+ * @param sizeAnimationSpec the specified [FiniteAnimationSpec] when animating pane size
+ * changes.
+ * @param positionAnimationSpec the specified [FiniteAnimationSpec] when animating pane position
+ * changes.
+ * @param delayedPositionAnimationSpec the specified [FiniteAnimationSpec] when animating pane
+ * position changes with a delay to emphasize entering panes.
+ */
+ fun copy(
+ primaryPaneMotion: PaneMotion = this.primaryPaneMotion,
+ secondaryPaneMotion: PaneMotion = this.secondaryPaneMotion,
+ tertiaryPaneMotion: PaneMotion = this.tertiaryPaneMotion,
+ sizeAnimationSpec: FiniteAnimationSpec<IntSize> = this.sizeAnimationSpec,
+ positionAnimationSpec: FiniteAnimationSpec<IntOffset> = this.positionAnimationSpec,
+ delayedPositionAnimationSpec: FiniteAnimationSpec<IntOffset> =
+ this.delayedPositionAnimationSpec
+ ): ThreePaneMotion =
+ ThreePaneMotion(
+ primaryPaneMotion,
+ secondaryPaneMotion,
+ tertiaryPaneMotion,
+ sizeAnimationSpec,
+ positionAnimationSpec,
+ delayedPositionAnimationSpec
+ )
/**
- * Resolves and returns the [EnterTransition] for the given [ThreePaneScaffoldRole] at the given
- * [ThreePaneScaffoldHorizontalOrder].
+ * Gets the specified [PaneMotion] of a given pane role.
+ *
+ * @param role the specified role of the pane, see [ListDetailPaneScaffoldRole] and
+ * [SupportingPaneScaffoldRole].
*/
- fun enterTransition(
- role: ThreePaneScaffoldRole,
- paneOrder: ThreePaneScaffoldHorizontalOrder
- ): EnterTransition {
- // Quick return in case this instance is the NoMotion one.
- if (this === NoMotion) return EnterTransition.None
-
- return when (paneOrder.indexOf(role)) {
- 0 -> firstPaneEnterTransition
- 1 -> secondPaneEnterTransition
- else -> thirdPaneEnterTransition
+ operator fun get(role: ThreePaneScaffoldRole): PaneMotion =
+ when (role) {
+ ThreePaneScaffoldRole.Primary -> primaryPaneMotion
+ ThreePaneScaffoldRole.Secondary -> secondaryPaneMotion
+ ThreePaneScaffoldRole.Tertiary -> tertiaryPaneMotion
}
- }
-
- /**
- * Resolves and returns the [ExitTransition] for the given [ThreePaneScaffoldRole] at the given
- * [ThreePaneScaffoldHorizontalOrder].
- */
- fun exitTransition(
- role: ThreePaneScaffoldRole,
- paneOrder: ThreePaneScaffoldHorizontalOrder
- ): ExitTransition {
- // Quick return in case this instance is the NoMotion one.
- if (this === NoMotion) return ExitTransition.None
-
- return when (paneOrder.indexOf(role)) {
- 0 -> firstPaneExitTransition
- 1 -> secondPaneExitTransition
- else -> thirdPaneExitTransition
- }
- }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ThreePaneMotion) return false
- if (this.positionAnimationSpec != other.positionAnimationSpec) return false
- if (this.sizeAnimationSpec != other.sizeAnimationSpec) return false
- if (this.firstPaneEnterTransition != other.firstPaneEnterTransition) return false
- if (this.firstPaneExitTransition != other.firstPaneExitTransition) return false
- if (this.secondPaneEnterTransition != other.secondPaneEnterTransition) return false
- if (this.secondPaneExitTransition != other.secondPaneExitTransition) return false
- if (this.thirdPaneEnterTransition != other.thirdPaneEnterTransition) return false
- if (this.thirdPaneExitTransition != other.thirdPaneExitTransition) return false
+ if (primaryPaneMotion != other.primaryPaneMotion) return false
+ if (secondaryPaneMotion != other.secondaryPaneMotion) return false
+ if (tertiaryPaneMotion != other.tertiaryPaneMotion) return false
+ if (sizeAnimationSpec != other.sizeAnimationSpec) return false
+ if (positionAnimationSpec != other.positionAnimationSpec) return false
+ if (delayedPositionAnimationSpec != other.delayedPositionAnimationSpec) return false
return true
}
override fun hashCode(): Int {
- var result = positionAnimationSpec.hashCode()
+ var result = primaryPaneMotion.hashCode()
+ result = 31 * result + secondaryPaneMotion.hashCode()
+ result = 31 * result + tertiaryPaneMotion.hashCode()
result = 31 * result + sizeAnimationSpec.hashCode()
- result = 31 * result + firstPaneEnterTransition.hashCode()
- result = 31 * result + firstPaneExitTransition.hashCode()
- result = 31 * result + secondPaneEnterTransition.hashCode()
- result = 31 * result + secondPaneExitTransition.hashCode()
- result = 31 * result + thirdPaneEnterTransition.hashCode()
- result = 31 * result + thirdPaneExitTransition.hashCode()
+ result = 31 * result + positionAnimationSpec.hashCode()
+ result = 31 * result + delayedPositionAnimationSpec.hashCode()
return result
}
+ override fun toString(): String {
+ return "ThreePaneMotion(" +
+ "primaryPaneMotion=$primaryPaneMotion, " +
+ "secondaryPaneMotion=$secondaryPaneMotion, " +
+ "tertiaryPaneMotion=$tertiaryPaneMotion, " +
+ "sizeAnimationSpec=$sizeAnimationSpec, " +
+ "positionAnimationSpec=$positionAnimationSpec, " +
+ "delayedPositionAnimationSpec=$delayedPositionAnimationSpec)"
+ }
+
+ internal fun toPaneMotionList(ltrOrder: ThreePaneScaffoldHorizontalOrder): List<PaneMotion> =
+ listOf(this[ltrOrder.firstPane], this[ltrOrder.secondPane], this[ltrOrder.thirdPane])
+
companion object {
- /**
- * A ThreePaneMotion with all transitions set to [EnterTransition.None] and
- * [ExitTransition.None].
- */
- val NoMotion = ThreePaneMotion()
-
- @JvmStatic
- protected fun slideInFromLeft(spacerSize: Int) =
- slideInHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpec) {
- -it - spacerSize
- }
-
- @JvmStatic
- protected fun slideInFromLeftDelayed(spacerSize: Int) =
- slideInHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpecDelayed) {
- -it - spacerSize
- }
-
- @JvmStatic
- protected fun slideInFromRight(spacerSize: Int) =
- slideInHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpec) {
- it + spacerSize
- }
-
- @JvmStatic
- protected fun slideInFromRightDelayed(spacerSize: Int) =
- slideInHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpecDelayed) {
- it + spacerSize
- }
-
- @JvmStatic
- protected fun slideOutToLeft(spacerSize: Int) =
- slideOutHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpec) {
- -it - spacerSize
- }
-
- @JvmStatic
- protected fun slideOutToRight(spacerSize: Int) =
- slideOutHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpec) {
- it + spacerSize
- }
- }
-}
-
-@ExperimentalMaterial3AdaptiveApi
-@Immutable
-internal class MovePanesToLeftMotion(private val spacerSize: Int) :
- ThreePaneMotion(
- ThreePaneMotionDefaults.PanePositionAnimationSpec,
- ThreePaneMotionDefaults.PaneSizeAnimationSpec,
- slideInFromRight(spacerSize),
- slideOutToLeft(spacerSize),
- slideInFromRight(spacerSize),
- slideOutToLeft(spacerSize),
- slideInFromRight(spacerSize),
- slideOutToLeft(spacerSize)
- ) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is MovePanesToLeftMotion) return false
- if (this.spacerSize != other.spacerSize) return false
- return true
- }
-
- override fun hashCode(): Int {
- return spacerSize
- }
-}
-
-@ExperimentalMaterial3AdaptiveApi
-@Immutable
-internal class MovePanesToRightMotion(private val spacerSize: Int) :
- ThreePaneMotion(
- ThreePaneMotionDefaults.PanePositionAnimationSpec,
- ThreePaneMotionDefaults.PaneSizeAnimationSpec,
- slideInFromLeft(spacerSize),
- slideOutToRight(spacerSize),
- slideInFromLeft(spacerSize),
- slideOutToRight(spacerSize),
- slideInFromLeft(spacerSize),
- slideOutToRight(spacerSize)
- ) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is MovePanesToRightMotion) return false
- if (this.spacerSize != other.spacerSize) return false
- return true
- }
-
- override fun hashCode(): Int {
- return spacerSize
- }
-}
-
-@ExperimentalMaterial3AdaptiveApi
-@Immutable
-internal class SwitchLeftTwoPanesMotion(private val spacerSize: Int) :
- ThreePaneMotion(
- ThreePaneMotionDefaults.PanePositionAnimationSpec,
- ThreePaneMotionDefaults.PaneSizeAnimationSpec,
- slideInFromLeftDelayed(spacerSize),
- slideOutToLeft(spacerSize),
- slideInFromLeftDelayed(spacerSize),
- slideOutToLeft(spacerSize),
- EnterTransition.None,
- ExitTransition.None
- ) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is SwitchLeftTwoPanesMotion) return false
- if (this.spacerSize != other.spacerSize) return false
- return true
- }
-
- override fun hashCode(): Int {
- return spacerSize
- }
-}
-
-@ExperimentalMaterial3AdaptiveApi
-@Immutable
-internal class SwitchRightTwoPanesMotion(private val spacerSize: Int) :
- ThreePaneMotion(
- ThreePaneMotionDefaults.PanePositionAnimationSpec,
- ThreePaneMotionDefaults.PaneSizeAnimationSpec,
- EnterTransition.None,
- ExitTransition.None,
- slideInFromRightDelayed(spacerSize),
- slideOutToRight(spacerSize),
- slideInFromRightDelayed(spacerSize),
- slideOutToRight(spacerSize)
- ) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is SwitchRightTwoPanesMotion) return false
- if (this.spacerSize != other.spacerSize) return false
- return true
- }
-
- override fun hashCode(): Int {
- return spacerSize
+ /** A default [ThreePaneMotion] instance that specifies no motions. */
+ val NoMotion =
+ ThreePaneMotion(
+ DefaultPaneMotion.NoMotion,
+ DefaultPaneMotion.NoMotion,
+ DefaultPaneMotion.NoMotion
+ )
}
}
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal fun calculateThreePaneMotion(
- previousScaffoldValue: ThreePaneScaffoldValue,
- currentScaffoldValue: ThreePaneScaffoldValue,
- paneOrder: ThreePaneScaffoldHorizontalOrder,
- spacerSize: Int
-): ThreePaneMotion {
- if (previousScaffoldValue.equals(currentScaffoldValue)) {
- return ThreePaneMotion.NoMotion
- }
- val previousExpandedCount = previousScaffoldValue.expandedCount
- val currentExpandedCount = currentScaffoldValue.expandedCount
- if (previousExpandedCount != currentExpandedCount) {
- // TODO(conradchen): Address this case
- return ThreePaneMotion.NoMotion
- }
- return when (previousExpandedCount) {
- 1 ->
- when (PaneAdaptedValue.Expanded) {
- previousScaffoldValue[paneOrder.firstPane] -> {
- MovePanesToLeftMotion(spacerSize)
- }
- previousScaffoldValue[paneOrder.thirdPane] -> {
- MovePanesToRightMotion(spacerSize)
- }
- currentScaffoldValue[paneOrder.thirdPane] -> {
- MovePanesToLeftMotion(spacerSize)
- }
- else -> {
- MovePanesToRightMotion(spacerSize)
- }
- }
- 2 ->
- when {
- previousScaffoldValue[paneOrder.firstPane] == PaneAdaptedValue.Expanded &&
- currentScaffoldValue[paneOrder.firstPane] == PaneAdaptedValue.Expanded -> {
- // The first pane stays, the right two panes switch
- SwitchRightTwoPanesMotion(spacerSize)
- }
- previousScaffoldValue[paneOrder.thirdPane] == PaneAdaptedValue.Expanded &&
- currentScaffoldValue[paneOrder.thirdPane] == PaneAdaptedValue.Expanded -> {
- // The third pane stays, the left two panes switch
- SwitchLeftTwoPanesMotion(spacerSize)
- }
+@Suppress("PrimitiveInCollection") // No way to get underlying Long of IntSize or IntOffset
+internal class ThreePaneScaffoldMotionScopeImpl : PaneScaffoldMotionScope {
+ internal lateinit var threePaneMotion: ThreePaneMotion
+ private set
- // Implies the second pane stays hereafter
- currentScaffoldValue[paneOrder.thirdPane] == PaneAdaptedValue.Expanded -> {
- // The third pane shows, all panes move left
- MovePanesToLeftMotion(spacerSize)
- }
- else -> {
- // The first pane shows, all panes move right
- MovePanesToRightMotion(spacerSize)
- }
- }
- else -> {
- // Should not happen
- ThreePaneMotion.NoMotion
- }
+ override val sizeAnimationSpec: FiniteAnimationSpec<IntSize>
+ get() = threePaneMotion.sizeAnimationSpec
+
+ override val positionAnimationSpec: FiniteAnimationSpec<IntOffset>
+ get() = threePaneMotion.positionAnimationSpec
+
+ override val delayedPositionAnimationSpec: FiniteAnimationSpec<IntOffset>
+ get() = threePaneMotion.delayedPositionAnimationSpec
+
+ override var scaffoldSize: IntSize = IntSize.Zero
+ override val paneMotionDataList: List<PaneMotionData> =
+ listOf(PaneMotionData(), PaneMotionData(), PaneMotionData())
+
+ internal fun updateThreePaneMotion(
+ threePaneMotion: ThreePaneMotion,
+ ltrOrder: ThreePaneScaffoldHorizontalOrder
+ ) {
+ val paneMotions = threePaneMotion.toPaneMotionList(ltrOrder)
+ this.paneMotionDataList.fastForEachIndexed { index, it -> it.motion = paneMotions[index] }
+ this.threePaneMotion = threePaneMotion
}
}
@@ -406,18 +360,23 @@
}
}
+/** The default settings of three pane motions. */
@ExperimentalMaterial3AdaptiveApi
-internal object ThreePaneMotionDefaults {
- // TODO(conradchen): open this to public when we support motion customization
- val PanePositionAnimationSpec: SpringSpec<IntOffset> =
+object ThreePaneMotionDefaults {
+ /** The default [FiniteAnimationSpec] of pane position animations. */
+ val PanePositionAnimationSpec: FiniteAnimationSpec<IntOffset> =
spring(
dampingRatio = 0.8f,
stiffness = 600f,
visibilityThreshold = IntOffset.VisibilityThreshold
)
- // TODO(conradchen): open this to public when we support motion customization
- val PanePositionAnimationSpecDelayed: DelayedSpringSpec<IntOffset> =
+ /**
+ * The default [FiniteAnimationSpec] of pane position animations with a delay. It's by default
+ * used in the case when an enter pane will intersect with exit panes, we delay the entering
+ * animation to emphasize the entering transition.
+ */
+ val PanePositionAnimationSpecDelayed: FiniteAnimationSpec<IntOffset> =
DelayedSpringSpec(
dampingRatio = 0.8f,
stiffness = 600f,
@@ -425,8 +384,8 @@
visibilityThreshold = IntOffset.VisibilityThreshold
)
- // TODO(conradchen): open this to public when we support motion customization
- val PaneSizeAnimationSpec: SpringSpec<IntSize> =
+ /** The default [FiniteAnimationSpec] of pane size animations. */
+ val PaneSizeAnimationSpec: FiniteAnimationSpec<IntSize> =
spring(
dampingRatio = 0.8f,
stiffness = 600f,
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
index 56f4c5f..bebc466 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
@@ -16,11 +16,6 @@
package androidx.compose.material3.adaptive.layout
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.Transition
-import androidx.compose.animation.core.snap
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -39,7 +34,6 @@
import androidx.compose.ui.layout.MultiContentMeasurePolicy
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.boundsInWindow
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
@@ -71,6 +65,7 @@
* panes.
* @param scaffoldValue The current adapted value of the scaffold.
* @param paneOrder The horizontal order of the panes from start to end in the scaffold.
+ * @param paneMotions The specified motion of the panes.
* @param secondaryPane The content of the secondary pane that has a priority lower then the primary
* pane but higher than the tertiary pane.
* @param tertiaryPane The content of the tertiary pane that has the lowest priority.
@@ -83,12 +78,13 @@
scaffoldDirective: PaneScaffoldDirective,
scaffoldValue: ThreePaneScaffoldValue,
paneOrder: ThreePaneScaffoldHorizontalOrder,
- secondaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
- tertiaryPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+ secondaryPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
+ tertiaryPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
+ paneMotions: ThreePaneMotion = calculateThreePaneMotion(scaffoldValue, paneOrder),
paneExpansionState: PaneExpansionState = rememberPaneExpansionState(),
paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
null,
- primaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+ primaryPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
) {
val scaffoldState = remember { ThreePaneScaffoldState(scaffoldValue) }
LaunchedEffect(key1 = scaffoldValue) { scaffoldState.animateTo(scaffoldValue) }
@@ -99,6 +95,7 @@
paneOrder = paneOrder,
secondaryPane = secondaryPane,
tertiaryPane = tertiaryPane,
+ paneMotions = paneMotions,
paneExpansionState = paneExpansionState,
paneExpansionDragHandle = paneExpansionDragHandle,
primaryPane = primaryPane
@@ -112,140 +109,73 @@
scaffoldDirective: PaneScaffoldDirective,
scaffoldState: ThreePaneScaffoldState,
paneOrder: ThreePaneScaffoldHorizontalOrder,
- secondaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
- tertiaryPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+ secondaryPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
+ tertiaryPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
+ paneMotions: ThreePaneMotion = scaffoldState.calculateThreePaneMotion(paneOrder),
paneExpansionState: PaneExpansionState = rememberPaneExpansionState(),
paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
null,
- primaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+ primaryPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
) {
val layoutDirection = LocalLayoutDirection.current
val ltrPaneOrder =
remember(paneOrder, layoutDirection) { paneOrder.toLtrOrder(layoutDirection) }
- val previousScaffoldValue = remember { ThreePaneScaffoldValueHolder(scaffoldState.targetState) }
- val spacerSize =
- with(LocalDensity.current) { scaffoldDirective.horizontalPartitionSpacerSize.roundToPx() }
- val paneMotion =
- remember(scaffoldState.targetState, ltrPaneOrder, spacerSize) {
- val previousValue = previousScaffoldValue.value
- previousScaffoldValue.value = scaffoldState.targetState
- calculateThreePaneMotion(
- previousScaffoldValue = previousValue,
- currentScaffoldValue = scaffoldState.targetState,
- paneOrder = ltrPaneOrder,
- spacerSize = spacerSize
- )
- }
+ val motionScope =
+ remember { ThreePaneScaffoldMotionScopeImpl() }
+ .apply { updateThreePaneMotion(paneMotions, ltrPaneOrder) }
val currentTransition = scaffoldState.rememberTransition()
+ val transitionScope =
+ remember { ThreePaneScaffoldTransitionScopeImpl() }
+ .apply {
+ transitionState = scaffoldState
+ scaffoldStateTransition = currentTransition
+ }
LookaheadScope {
+ val scaffoldScope =
+ remember(currentTransition, this) {
+ ThreePaneScaffoldScopeImpl(motionScope, transitionScope, this)
+ }
// Create PaneWrappers for each of the panes and map the transitions according to each pane
// role and order.
val contents =
listOf<@Composable () -> Unit>(
{
- remember(currentTransition, this@LookaheadScope) {
- ThreePaneScaffoldScopeImpl(
+ remember(scaffoldScope) {
+ ThreePaneScaffoldPaneScopeImpl(
ThreePaneScaffoldRole.Primary,
- currentTransition,
- scaffoldState,
- this@LookaheadScope
+ scaffoldScope
)
}
- .apply {
- positionAnimationSpec = paneMotion.positionAnimationSpec
- sizeAnimationSpec = paneMotion.sizeAnimationSpec
- enterTransition =
- paneMotion.enterTransition(
- ThreePaneScaffoldRole.Primary,
- ltrPaneOrder
- )
- exitTransition =
- paneMotion.exitTransition(
- ThreePaneScaffoldRole.Primary,
- ltrPaneOrder
- )
- }
+ .apply { updatePaneMotion(paneMotions) }
.primaryPane()
},
{
- remember(currentTransition, this@LookaheadScope) {
- ThreePaneScaffoldScopeImpl(
+ remember(scaffoldScope) {
+ ThreePaneScaffoldPaneScopeImpl(
ThreePaneScaffoldRole.Secondary,
- currentTransition,
- scaffoldState,
- this@LookaheadScope
+ scaffoldScope
)
}
- .apply {
- positionAnimationSpec = paneMotion.positionAnimationSpec
- sizeAnimationSpec = paneMotion.sizeAnimationSpec
- enterTransition =
- paneMotion.enterTransition(
- ThreePaneScaffoldRole.Secondary,
- ltrPaneOrder
- )
- exitTransition =
- paneMotion.exitTransition(
- ThreePaneScaffoldRole.Secondary,
- ltrPaneOrder
- )
- }
+ .apply { updatePaneMotion(paneMotions) }
.secondaryPane()
},
{
if (tertiaryPane != null) {
- remember(currentTransition, this@LookaheadScope) {
- ThreePaneScaffoldScopeImpl(
+ remember(scaffoldScope) {
+ ThreePaneScaffoldPaneScopeImpl(
ThreePaneScaffoldRole.Tertiary,
- currentTransition,
- scaffoldState,
- this@LookaheadScope
+ scaffoldScope
)
}
- .apply {
- positionAnimationSpec = paneMotion.positionAnimationSpec
- sizeAnimationSpec = paneMotion.sizeAnimationSpec
- enterTransition =
- paneMotion.enterTransition(
- ThreePaneScaffoldRole.Tertiary,
- ltrPaneOrder
- )
- exitTransition =
- paneMotion.exitTransition(
- ThreePaneScaffoldRole.Tertiary,
- ltrPaneOrder
- )
- }
+ .apply { updatePaneMotion(paneMotions) }
.tertiaryPane()
}
},
{
if (paneExpansionDragHandle != null) {
- remember(currentTransition, this@LookaheadScope) {
- ThreePaneScaffoldScopeImpl(
- ThreePaneScaffoldRole.Tertiary,
- currentTransition,
- scaffoldState,
- this@LookaheadScope
- )
- }
- .apply {
- positionAnimationSpec = paneMotion.positionAnimationSpec
- sizeAnimationSpec = paneMotion.sizeAnimationSpec
- enterTransition =
- paneMotion.enterTransition(
- ThreePaneScaffoldRole.Tertiary,
- ltrPaneOrder
- )
- exitTransition =
- paneMotion.exitTransition(
- ThreePaneScaffoldRole.Tertiary,
- ltrPaneOrder
- )
- }
- .paneExpansionDragHandle(paneExpansionState)
+ scaffoldScope.paneExpansionDragHandle(paneExpansionState)
}
}
)
@@ -257,6 +187,7 @@
scaffoldState.targetState,
paneExpansionState,
ltrPaneOrder,
+ motionScope
)
}
.apply {
@@ -270,32 +201,17 @@
}
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private class ThreePaneScaffoldValueHolder(var value: ThreePaneScaffoldValue)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
private class ThreePaneContentMeasurePolicy(
scaffoldDirective: PaneScaffoldDirective,
scaffoldValue: ThreePaneScaffoldValue,
val paneExpansionState: PaneExpansionState,
paneOrder: ThreePaneScaffoldHorizontalOrder,
+ val paneMotionScope: ThreePaneScaffoldMotionScopeImpl
) : MultiContentMeasurePolicy {
var scaffoldDirective by mutableStateOf(scaffoldDirective)
var scaffoldValue by mutableStateOf(scaffoldValue)
var paneOrder by mutableStateOf(paneOrder)
- /**
- * Data class that is used to store the position and width of an expanded pane to be reused when
- * the pane is being hidden.
- */
- data class PanePlacement(var positionX: Int = 0, var measuredWidth: Int = 0)
-
- private val placementsCache =
- mapOf(
- ThreePaneScaffoldRole.Primary to PanePlacement(),
- ThreePaneScaffoldRole.Secondary to PanePlacement(),
- ThreePaneScaffoldRole.Tertiary to PanePlacement()
- )
-
override fun MeasureScope.measure(
measurables: List<List<Measurable>>,
constraints: Constraints
@@ -308,6 +224,7 @@
if (coordinates == null) {
return@layout
}
+ paneMotionScope.scaffoldSize = IntSize(constraints.maxWidth, constraints.maxHeight)
val visiblePanes =
getPanesMeasurables(
paneOrder = paneOrder,
@@ -628,12 +545,12 @@
) {
with(measurable) {
measureAndPlace(
- localBounds.width,
- localBounds.height,
- localBounds.left,
- localBounds.top,
- if (isLookingAhead) placementsCache else null
- )
+ localBounds.width,
+ localBounds.height,
+ localBounds.left,
+ localBounds.top,
+ )
+ .save(measurable.role, isLookingAhead)
}
}
@@ -675,12 +592,12 @@
measurables.fastForEach {
with(it) {
measureAndPlace(
- it.measuringWidth,
- partitionBounds.height,
- positionX,
- partitionBounds.top,
- if (isLookingAhead) placementsCache else null
- )
+ it.measuringWidth,
+ partitionBounds.height,
+ positionX,
+ partitionBounds.top,
+ )
+ .save(it.role, isLookingAhead)
}
positionX += it.measuredWidth + spacerSize
}
@@ -699,20 +616,30 @@
// When panes are not animated, we don't need to measure and place them.
return
}
- val cachedPanePlacement = placementsCache[it.role]!!
with(it) {
+ val measuredData = paneMotionScope.paneMotionDataList[paneOrder.indexOf(it.role)]
measureAndPlace(
- cachedPanePlacement.measuredWidth,
+ measuredData.targetSize.width,
partitionHeight,
- cachedPanePlacement.positionX,
+ measuredData.targetPosition.x,
partitionTop,
- null,
ThreePaneScaffoldDefaults.HiddenPaneZIndex
)
}
}
}
+ private fun PaneMeasurement.save(role: ThreePaneScaffoldRole, isLookingAhead: Boolean) {
+ val paneMotionData = paneMotionScope.paneMotionDataList[paneOrder.indexOf(role)]
+ if (isLookingAhead) {
+ paneMotionData.targetSize = this.size
+ paneMotionData.targetPosition = this.offset
+ } else {
+ paneMotionData.currentSize = this.size
+ paneMotionData.currentPosition = this.offset
+ }
+ }
+
private fun Placeable.PlacementScope.getLocalBounds(bounds: Rect): IntRect {
return bounds.translate(coordinates!!.windowToLocal(Offset.Zero)).roundToIntRect()
}
@@ -788,9 +715,8 @@
height: Int,
positionX: Int,
positionY: Int,
- placementsCache: Map<ThreePaneScaffoldRole, ThreePaneContentMeasurePolicy.PanePlacement>?,
zIndex: Float = 0f
- ) {
+ ): PaneMeasurement {
measuredWidth = width
measuredHeight = height
placedPositionX = positionX
@@ -798,73 +724,11 @@
measurable.measure(Constraints.fixed(width, height)).place(positionX, positionY, zIndex)
measuredAndPlaced = true
- // Cache the values to be used when this measurable's role is being hidden.
- // See placeHiddenPanes.
- if (placementsCache != null) {
- val cachedPanePlacement = placementsCache[role]!!
- cachedPanePlacement.measuredWidth = width
- cachedPanePlacement.positionX = positionX
- }
+ return PaneMeasurement(IntSize(width, height), IntOffset(positionX, positionY))
}
}
-/** Scope for the panes of [ThreePaneScaffold]. */
-@ExperimentalMaterial3AdaptiveApi
-sealed interface ThreePaneScaffoldScope : PaneScaffoldScope, LookaheadScope {
- /** The [ThreePaneScaffoldRole] of the current pane in the scope. */
- val role: ThreePaneScaffoldRole
-
- /** The current scaffold state transition between [ThreePaneScaffoldValue]s. */
- val scaffoldStateTransition: Transition<ThreePaneScaffoldValue>
-
- /** The current fraction of the scaffold state transition. */
- val scaffoldStateTransitionFraction: Float
-
- /**
- * The position animation spec of the associated pane to the scope. [AnimatedPane] will use this
- * value to perform pane animations during scaffold state changes.
- */
- val positionAnimationSpec: FiniteAnimationSpec<IntOffset>
-
- /**
- * The size animation spec of the associated pane to the scope. [AnimatedPane] will use this
- * value to perform pane animations during scaffold state changes.
- */
- val sizeAnimationSpec: FiniteAnimationSpec<IntSize>
-
- /**
- * The [EnterTransition] of the associated pane. [AnimatedPane] will use this value to perform
- * pane entering animations when it's showing during scaffold state changes.
- */
- val enterTransition: EnterTransition
-
- /**
- * The [ExitTransition] of the associated pane. [AnimatedPane] will use this value to perform
- * pane exiting animations when it's hiding during scaffold state changes.
- */
- val exitTransition: ExitTransition
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private class ThreePaneScaffoldScopeImpl(
- override val role: ThreePaneScaffoldRole,
- override val scaffoldStateTransition: Transition<ThreePaneScaffoldValue>,
- private val scaffoldState: ThreePaneScaffoldState,
- lookaheadScope: LookaheadScope
-) : ThreePaneScaffoldScope, LookaheadScope by lookaheadScope, PaneScaffoldScopeImpl() {
- override val scaffoldStateTransitionFraction: Float
- get() =
- if (scaffoldState.currentState == scaffoldState.targetState) {
- 1f
- } else {
- scaffoldState.progressFraction
- }
-
- override var positionAnimationSpec: FiniteAnimationSpec<IntOffset> by mutableStateOf(snap())
- override var sizeAnimationSpec: FiniteAnimationSpec<IntSize> by mutableStateOf(snap())
- override var enterTransition by mutableStateOf(EnterTransition.None)
- override var exitTransition by mutableStateOf(ExitTransition.None)
-}
+private data class PaneMeasurement(val size: IntSize, val offset: IntOffset)
/**
* Provides default values of [ThreePaneScaffold] and the calculation functions of
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldDestinationItem.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldDestinationItem.kt
index 3c0b995..8ebafe4 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldDestinationItem.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldDestinationItem.kt
@@ -22,31 +22,31 @@
* An item representing a navigation destination in a [ThreePaneScaffold].
*
* @param pane the pane destination of the navigation.
- * @param content the optional content, or an id representing the content of the destination. The
- * type [T] must be storable in a Bundle.
+ * @param contentKey the optional key or id representing the content of the destination. The type
+ * [T] must be storable in a Bundle.
*/
@ExperimentalMaterial3AdaptiveApi
class ThreePaneScaffoldDestinationItem<out T>(
val pane: ThreePaneScaffoldRole,
- val content: T? = null,
+ val contentKey: T? = null,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ThreePaneScaffoldDestinationItem<*>) return false
if (pane != other.pane) return false
- if (content != other.content) return false
+ if (contentKey != other.contentKey) return false
return true
}
override fun hashCode(): Int {
var result = pane.hashCode()
- result = 31 * result + (content?.hashCode() ?: 0)
+ result = 31 * result + (contentKey?.hashCode() ?: 0)
return result
}
override fun toString(): String {
- return "ThreePaneScaffoldDestinationItem(pane=$pane, content=$content)"
+ return "ThreePaneScaffoldDestinationItem(pane=$pane, contentKey=$contentKey)"
}
}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt
new file mode 100644
index 0000000..8454908
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024 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.adaptive.layout
+
+import androidx.compose.animation.core.Transition
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.layout.LookaheadScope
+
+/** Scope for the panes of [ThreePaneScaffold]. */
+@ExperimentalMaterial3AdaptiveApi
+sealed interface ThreePaneScaffoldScope :
+ ExtendedPaneScaffoldScope<ThreePaneScaffoldRole, ThreePaneScaffoldValue>
+
+/** Scope for the panes of [ThreePaneScaffold]. */
+@ExperimentalMaterial3AdaptiveApi
+sealed interface ThreePaneScaffoldPaneScope :
+ ThreePaneScaffoldScope,
+ ExtendedPaneScaffoldPaneScope<ThreePaneScaffoldRole, ThreePaneScaffoldValue>
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal class ThreePaneScaffoldScopeImpl(
+ motionScope: PaneScaffoldMotionScope,
+ transitionScope: PaneScaffoldTransitionScope<ThreePaneScaffoldRole, ThreePaneScaffoldValue>,
+ lookaheadScope: LookaheadScope
+) :
+ ThreePaneScaffoldScope,
+ PaneScaffoldMotionScope by motionScope,
+ PaneScaffoldTransitionScope<ThreePaneScaffoldRole, ThreePaneScaffoldValue> by transitionScope,
+ LookaheadScope by lookaheadScope,
+ PaneScaffoldScopeImpl()
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal class ThreePaneScaffoldPaneScopeImpl(
+ override val paneRole: ThreePaneScaffoldRole,
+ scaffoldScope: ThreePaneScaffoldScope,
+) : ThreePaneScaffoldPaneScope, ThreePaneScaffoldScope by scaffoldScope {
+ override var paneMotion: PaneMotion by mutableStateOf(DefaultPaneMotion.ExitToLeft)
+ private set
+
+ fun updatePaneMotion(paneMotions: ThreePaneMotion) {
+ paneMotion = paneMotions[paneRole]
+ }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal class ThreePaneScaffoldTransitionScopeImpl :
+ PaneScaffoldTransitionScope<ThreePaneScaffoldRole, ThreePaneScaffoldValue> {
+ override val motionProgress: Float
+ get() =
+ if (transitionState.currentState == transitionState.targetState) {
+ 1f
+ } else {
+ transitionState.progressFraction
+ }
+
+ override lateinit var scaffoldStateTransition: Transition<ThreePaneScaffoldValue>
+ lateinit var transitionState: ThreePaneScaffoldState
+}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt
index b7b8385..6363568 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldState.kt
@@ -24,12 +24,14 @@
import androidx.compose.foundation.MutatorMutex
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
/**
* The state of a three pane scaffold. It serves as the [SeekableTransitionState] to manipulate the
* [Transition] between [ThreePaneScaffoldValue]s.
*/
@ExperimentalMaterial3AdaptiveApi
+@Stable
class ThreePaneScaffoldState
internal constructor(
private val transitionState: SeekableTransitionState<ThreePaneScaffoldValue>,
diff --git a/compose/material3/adaptive/adaptive-navigation/api/current.txt b/compose/material3/adaptive/adaptive-navigation/api/current.txt
index 0737677..4612aff 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/current.txt
@@ -2,8 +2,8 @@
package androidx.compose.material3.adaptive.navigation {
public final class AndroidThreePaneScaffold_androidKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class BackNavigationBehavior {
@@ -28,7 +28,7 @@
method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getScaffoldValue();
method public boolean isDestinationHistoryAware();
method public boolean navigateBack(optional String backNavigationBehavior);
- method public void navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? content);
+ method public void navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? contentKey);
method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue peekPreviousScaffoldValue(optional String backNavigationBehavior);
method public void setDestinationHistoryAware(boolean);
property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? currentDestination;
@@ -38,9 +38,9 @@
}
public final class ThreePaneScaffoldNavigatorKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
}
diff --git a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
index 0737677..4612aff 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
@@ -2,8 +2,8 @@
package androidx.compose.material3.adaptive.navigation {
public final class AndroidThreePaneScaffold_androidKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional androidx.compose.material3.adaptive.layout.ThreePaneMotion paneMotions, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class BackNavigationBehavior {
@@ -28,7 +28,7 @@
method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue getScaffoldValue();
method public boolean isDestinationHistoryAware();
method public boolean navigateBack(optional String backNavigationBehavior);
- method public void navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? content);
+ method public void navigateTo(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane, optional T? contentKey);
method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue peekPreviousScaffoldValue(optional String backNavigationBehavior);
method public void setDestinationHistoryAware(boolean);
property public abstract androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<T>? currentDestination;
@@ -38,9 +38,9 @@
}
public final class ThreePaneScaffoldNavigatorKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
}
diff --git a/compose/material3/adaptive/adaptive-navigation/build.gradle b/compose/material3/adaptive/adaptive-navigation/build.gradle
index 67fef66..dbbcdc2 100644
--- a/compose/material3/adaptive/adaptive-navigation/build.gradle
+++ b/compose/material3/adaptive/adaptive-navigation/build.gradle
@@ -78,7 +78,7 @@
dependencies {
implementation(project(":compose:material3:material3"))
implementation(project(":compose:test-utils"))
- implementation(project(":window:window-testing"))
+ implementation("androidx.window:window-testing:1.3.0")
implementation(libs.junit)
implementation(libs.testRunner)
implementation(libs.truth)
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
index 54e2cf5..5296dfd 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
@@ -63,7 +63,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(canNavigateBack).isTrue()
}
}
@@ -92,7 +92,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(canNavigateBack).isFalse()
}
}
@@ -122,7 +122,7 @@
.isEqualTo(PaneAdaptedValue.Hidden)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Extra)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(canNavigateBack).isTrue()
}
}
@@ -152,7 +152,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Extra)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(canNavigateBack).isTrue()
}
}
@@ -177,7 +177,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(canNavigateBack).isTrue()
scaffoldNavigator.navigateBack()
}
@@ -187,7 +187,7 @@
.isEqualTo(PaneAdaptedValue.Hidden)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.List)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
assertThat(canNavigateBack).isFalse()
}
}
@@ -211,7 +211,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
}
}
@@ -237,7 +237,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopLatest)).isTrue()
scaffoldNavigator.navigateBack(BackNavigationBehavior.PopLatest)
}
@@ -247,7 +247,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.List)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
}
}
@@ -271,7 +271,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
assertThat(
scaffoldNavigator.canNavigateBack(
BackNavigationBehavior.PopUntilCurrentDestinationChange
@@ -284,7 +284,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.List)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
}
}
@@ -307,7 +307,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
assertThat(
scaffoldNavigator.canNavigateBack(
BackNavigationBehavior.PopUntilCurrentDestinationChange
@@ -337,7 +337,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
assertThat(
scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
)
@@ -348,7 +348,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
}
}
@@ -372,7 +372,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Extra)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(
scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
)
@@ -383,7 +383,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
}
}
@@ -406,7 +406,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(
scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
)
@@ -439,7 +439,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.List)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
}
@@ -451,7 +451,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
scaffoldNavigator.navigateBack()
}
@@ -463,7 +463,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.List)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
}
}
@@ -492,7 +492,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.List)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, 0)
}
@@ -504,7 +504,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
scaffoldNavigator.navigateBack()
}
@@ -516,7 +516,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Extra)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
}
}
@@ -664,7 +664,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
// Switches to dual pane
mockCurrentScaffoldDirective.value = MockDualPaneScaffoldDirective
}
@@ -673,7 +673,7 @@
assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(ListDetailPaneScaffoldRole.Detail)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
}
}
}
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
index d8c1561..ef9c1bf 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
@@ -63,7 +63,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(canNavigateBack).isTrue()
}
}
@@ -92,7 +92,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Main)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
assertThat(canNavigateBack).isFalse()
}
}
@@ -123,7 +123,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Extra, 1)
}
@@ -132,7 +132,7 @@
.isEqualTo(PaneAdaptedValue.Hidden)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Extra)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
assertThat(canNavigateBack).isTrue()
}
}
@@ -163,7 +163,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Extra, 1)
}
@@ -172,7 +172,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Extra)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
assertThat(canNavigateBack).isTrue()
}
}
@@ -199,7 +199,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(canNavigateBack).isTrue()
scaffoldNavigator.navigateBack()
}
@@ -209,7 +209,7 @@
.isEqualTo(PaneAdaptedValue.Hidden)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Main)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
assertThat(canNavigateBack).isFalse()
}
}
@@ -236,7 +236,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Main)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
}
}
@@ -265,7 +265,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Main)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
assertThat(scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopLatest)).isTrue()
scaffoldNavigator.navigateBack(BackNavigationBehavior.PopLatest)
}
@@ -275,7 +275,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
}
}
@@ -305,7 +305,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
assertThat(
scaffoldNavigator.canNavigateBack(
BackNavigationBehavior.PopUntilCurrentDestinationChange
@@ -318,7 +318,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Main)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
}
}
@@ -341,7 +341,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Main)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
assertThat(
scaffoldNavigator.canNavigateBack(
BackNavigationBehavior.PopUntilCurrentDestinationChange
@@ -377,7 +377,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
assertThat(
scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
)
@@ -388,7 +388,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
}
}
@@ -415,7 +415,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Extra)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(
scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
)
@@ -426,7 +426,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
}
}
@@ -452,7 +452,7 @@
composeRule.runOnIdle {
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
assertThat(
scaffoldNavigator.canNavigateBack(BackNavigationBehavior.PopUntilContentChange)
)
@@ -488,7 +488,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Main)
}
@@ -500,7 +500,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Main)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
scaffoldNavigator.navigateBack()
}
@@ -512,7 +512,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
}
}
@@ -544,7 +544,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Supporting)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(0)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(0)
scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Main)
}
@@ -556,7 +556,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Main)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
scaffoldNavigator.navigateBack()
}
@@ -568,7 +568,7 @@
)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Extra)
- assertThat(scaffoldNavigator.currentDestination?.content).isEqualTo(1)
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isEqualTo(1)
}
}
@@ -728,7 +728,7 @@
.isEqualTo(PaneAdaptedValue.Expanded)
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Main)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
// Switches to dual pane
mockCurrentScaffoldDirective.value = MockDualPaneScaffoldDirective
}
@@ -737,7 +737,7 @@
assertThat(scaffoldNavigator.canNavigateBack()).isFalse()
assertThat(scaffoldNavigator.currentDestination?.pane)
.isEqualTo(SupportingPaneScaffoldRole.Main)
- assertThat(scaffoldNavigator.currentDestination?.content).isNull()
+ assertThat(scaffoldNavigator.currentDestination?.contentKey).isNull()
}
}
}
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
index 17e580c..859c8b8 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
@@ -22,7 +22,11 @@
import androidx.compose.material3.adaptive.layout.PaneExpansionDragHandle
import androidx.compose.material3.adaptive.layout.PaneExpansionState
import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold as BaseSupportingPaneScaffold
+import androidx.compose.material3.adaptive.layout.ThreePaneMotion
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope
+import androidx.compose.material3.adaptive.layout.calculateListDetailPaneScaffoldMotion
+import androidx.compose.material3.adaptive.layout.calculateSupportingPaneScaffoldMotion
import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -44,6 +48,8 @@
* mail app. See [ListDetailPaneScaffoldRole.Extra].
* @param defaultBackBehavior the default back navigation behavior when the system back event
* happens. See [BackNavigationBehavior] for the use cases of each behavior.
+ * @param paneMotions The specified motion of the panes. By default the value will be calculated by
+ * [calculateListDetailPaneScaffoldMotion] according to the target [ThreePaneScaffoldValue].
* @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
* change pane expansion state. Note that by default this argument will be `null`, and there won't
* be a drag handle rendered and users won't be able to drag to change the pane split. You can
@@ -56,11 +62,12 @@
@Composable
fun NavigableListDetailPaneScaffold(
navigator: ThreePaneScaffoldNavigator<Any>,
- listPane: @Composable ThreePaneScaffoldScope.() -> Unit,
- detailPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+ listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
+ detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
modifier: Modifier = Modifier,
- extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+ extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange,
+ paneMotions: ThreePaneMotion = calculateListDetailPaneScaffoldMotion(navigator.scaffoldValue),
paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
null,
paneExpansionState: PaneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue),
@@ -76,6 +83,7 @@
detailPane = detailPane,
listPane = listPane,
extraPane = extraPane,
+ paneMotions = paneMotions,
paneExpansionDragHandle = paneExpansionDragHandle,
paneExpansionState = paneExpansionState,
)
@@ -97,6 +105,8 @@
* [SupportingPaneScaffoldRole.Extra].
* @param defaultBackBehavior the default back navigation behavior when the system back event
* happens. See [BackNavigationBehavior] for the use cases of each behavior.
+ * @param paneMotions The specified motion of the panes. By default the value will be calculated by
+ * [calculateSupportingPaneScaffoldMotion] according to the target [ThreePaneScaffoldValue].
* @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
* change pane expansion state. Note that by default this argument will be `null`, and there won't
* be a drag handle rendered and users won't be able to drag to change the pane split. You can
@@ -109,11 +119,12 @@
@Composable
fun NavigableSupportingPaneScaffold(
navigator: ThreePaneScaffoldNavigator<Any>,
- mainPane: @Composable ThreePaneScaffoldScope.() -> Unit,
- supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit,
+ mainPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
+ supportingPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
modifier: Modifier = Modifier,
- extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+ extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange,
+ paneMotions: ThreePaneMotion = calculateSupportingPaneScaffoldMotion(navigator.scaffoldValue),
paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
null,
paneExpansionState: PaneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue),
@@ -129,6 +140,7 @@
mainPane = mainPane,
supportingPane = supportingPane,
extraPane = extraPane,
+ paneMotions = paneMotions,
paneExpansionDragHandle = paneExpansionDragHandle,
paneExpansionState = paneExpansionState,
)
diff --git a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/BackNavigationBehavior.kt b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/BackNavigationBehavior.kt
index e3ba0c4..66f554d7 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/BackNavigationBehavior.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/BackNavigationBehavior.kt
@@ -55,7 +55,7 @@
/**
* Pop destinations from the backstack until there is a content change.
*
- * A "content change" is defined as either a change in the content of the current
+ * A "content change" is defined as either a change in the `contentKey` of the current
* [ThreePaneScaffoldDestinationItem], or a change in the scaffold value (similar to
* [PopUntilScaffoldValueChange]).
*/
diff --git a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
index 81a8de2..42378e4 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
@@ -59,9 +59,9 @@
* and the default implementation to get better understanding and address the intricacies of
* navigation in an adaptive scenario.
*
- * @param T the type representing the content, or id of the content, for a navigation destination.
- * This type must be storable in a Bundle. Used to customize navigation behavior (for example,
- * [BackNavigationBehavior]). If this customization is unneeded, you can pass [Nothing].
+ * @param T the type representing the content key/id for a navigation destination. This type must be
+ * storable in a Bundle. Used to customize navigation behavior (for example,
+ * [BackNavigationBehavior]). If this customization is unneeded, you can pass [Any].
*/
@ExperimentalMaterial3AdaptiveApi
@Stable
@@ -116,10 +116,9 @@
* pane currently being used.
*
* @param pane the new destination pane.
- * @param content the optional content, or an id representing the content of the new
- * destination.
+ * @param contentKey the optional key or id representing the content of the new destination.
*/
- fun navigateTo(pane: ThreePaneScaffoldRole, content: T? = null)
+ fun navigateTo(pane: ThreePaneScaffoldRole, contentKey: T? = null)
/**
* Returns `true` if there is a previous destination to navigate back to.
@@ -157,9 +156,9 @@
* default navigator is supposed to be used independently from any navigation frameworks and handles
* the navigation purely inside the [ListDetailPaneScaffold].
*
- * @param T the type representing the content, or id of the content, for a navigation destination.
- * This type must be storable in a Bundle. Used to customize navigation behavior (for example,
- * [BackNavigationBehavior]). If this customization is unneeded, you can pass [Nothing].
+ * @param T the type representing the content key/id for a navigation destination. This type must be
+ * storable in a Bundle. Used to customize navigation behavior (for example,
+ * [BackNavigationBehavior]). If this customization is unneeded, you can pass [Any].
* @param scaffoldDirective the current layout directives to follow. The default value will be
* calculated with [calculatePaneScaffoldDirective] using
* [WindowAdaptiveInfo][androidx.compose.material3.adaptive.WindowAdaptiveInfo] retrieved from the
@@ -212,8 +211,8 @@
adaptStrategies: ThreePaneScaffoldAdaptStrategies =
ListDetailPaneScaffoldDefaults.adaptStrategies(),
isDestinationHistoryAware: Boolean = true,
-): ThreePaneScaffoldNavigator<Nothing> =
- rememberListDetailPaneScaffoldNavigator<Nothing>(
+): ThreePaneScaffoldNavigator<Any> =
+ rememberListDetailPaneScaffoldNavigator<Any>(
scaffoldDirective,
adaptStrategies,
isDestinationHistoryAware,
@@ -225,9 +224,9 @@
* default navigator is supposed to be used independently from any navigation frameworks and handles
* the navigation purely inside the [SupportingPaneScaffold].
*
- * @param T the type representing the content, or id of the content, for a navigation destination.
- * This type must be storable in a Bundle. Used to customize navigation behavior (for example,
- * [BackNavigationBehavior]). If this customization is unneeded, you can pass [Nothing].
+ * @param T the type representing the content key/id for a navigation destination. This type must be
+ * storable in a Bundle. Used to customize navigation behavior (for example,
+ * [BackNavigationBehavior]). If this customization is unneeded, you can pass [Any].
* @param scaffoldDirective the current layout directives to follow. The default value will be
* calculated with [calculatePaneScaffoldDirective] using
* [WindowAdaptiveInfo][androidx.compose.material3.adaptive.WindowAdaptiveInfo] retrieved from the
@@ -280,8 +279,8 @@
adaptStrategies: ThreePaneScaffoldAdaptStrategies =
SupportingPaneScaffoldDefaults.adaptStrategies(),
isDestinationHistoryAware: Boolean = true,
-): ThreePaneScaffoldNavigator<Nothing> =
- rememberSupportingPaneScaffoldNavigator<Nothing>(
+): ThreePaneScaffoldNavigator<Any> =
+ rememberSupportingPaneScaffoldNavigator<Any>(
scaffoldDirective,
adaptStrategies,
isDestinationHistoryAware,
@@ -349,8 +348,8 @@
return if (index == -1) scaffoldValue else calculateScaffoldValue(index)
}
- override fun navigateTo(pane: ThreePaneScaffoldRole, content: T?) {
- destinationHistory.add(ThreePaneScaffoldDestinationItem(pane, content))
+ override fun navigateTo(pane: ThreePaneScaffoldRole, contentKey: T?) {
+ destinationHistory.add(ThreePaneScaffoldDestinationItem(pane, contentKey))
}
override fun canNavigateBack(backNavigationBehavior: BackNavigationBehavior): Boolean =
@@ -392,8 +391,8 @@
}
BackNavigationBehavior.PopUntilContentChange ->
for (previousDestinationIndex in destinationHistory.lastIndex - 1 downTo 0) {
- val content = destinationHistory[previousDestinationIndex].content
- if (content != currentDestination?.content) {
+ val contentKey = destinationHistory[previousDestinationIndex].contentKey
+ if (contentKey != currentDestination?.contentKey) {
return previousDestinationIndex
}
// A scaffold value change also counts as a content change.
@@ -461,12 +460,12 @@
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
internal fun <T> destinationItemSaver(): Saver<ThreePaneScaffoldDestinationItem<T>, Any> =
listSaver(
- save = { listOf(it.pane, it.content) },
+ save = { listOf(it.pane, it.contentKey) },
restore = {
@Suppress("UNCHECKED_CAST")
(ThreePaneScaffoldDestinationItem(
pane = it[0] as ThreePaneScaffoldRole,
- content = it[1] as T?
+ contentKey = it[1] as T?
))
}
)
diff --git a/activity/activity-ktx/api/1.10.0-beta01.txt b/compose/material3/adaptive/adaptive-render-strategy/api/current.txt
similarity index 100%
rename from activity/activity-ktx/api/1.10.0-beta01.txt
rename to compose/material3/adaptive/adaptive-render-strategy/api/current.txt
diff --git a/activity/activity/api/res-1.10.0-beta01.txt b/compose/material3/adaptive/adaptive-render-strategy/api/res-current.txt
similarity index 100%
rename from activity/activity/api/res-1.10.0-beta01.txt
rename to compose/material3/adaptive/adaptive-render-strategy/api/res-current.txt
diff --git a/activity/activity-ktx/api/1.10.0-beta01.txt b/compose/material3/adaptive/adaptive-render-strategy/api/restricted_current.txt
similarity index 100%
copy from activity/activity-ktx/api/1.10.0-beta01.txt
copy to compose/material3/adaptive/adaptive-render-strategy/api/restricted_current.txt
diff --git a/compose/material3/adaptive/adaptive-render-strategy/build.gradle b/compose/material3/adaptive/adaptive-render-strategy/build.gradle
new file mode 100644
index 0000000..7634ff6
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-render-strategy/build.gradle
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+/**
+ * This file was created using the `create_project.py` script located in the
+ * `<AndroidX root>/development/project-creator` directory.
+ *
+ * Please use that script when creating a new project, rather than copying an existing project and
+ * modifying its settings.
+ */
+
+import androidx.build.LibraryType
+import androidx.build.PlatformIdentifier
+
+plugins {
+ id("AndroidXPlugin")
+ id("AndroidXComposePlugin")
+ id("com.android.library")
+}
+
+androidXMultiplatform {
+ android()
+ jvmStubs()
+
+ defaultPlatform(PlatformIdentifier.ANDROID)
+
+ sourceSets {
+ commonMain {
+ dependencies {
+ implementation(libs.kotlinStdlib)
+ }
+ }
+
+ commonTest {
+ dependencies {
+ }
+ }
+
+ jvmMain {
+ dependsOn(commonMain)
+ dependencies {
+ implementation(libs.testRules)
+ implementation(libs.testRunner)
+ implementation(libs.junit)
+ implementation(libs.truth)
+ }
+ }
+
+ androidMain {
+ dependsOn(jvmMain)
+ dependencies {
+ api("androidx.annotation:annotation:1.8.1")
+ }
+ }
+
+ jvmTest {
+ dependsOn(commonTest)
+ dependencies {
+ }
+ }
+
+ androidInstrumentedTest {
+ dependsOn(jvmTest)
+ dependencies {
+ implementation(libs.testRules)
+ implementation(libs.testRunner)
+ implementation(libs.junit)
+ implementation(libs.truth)
+ }
+ }
+
+ commonStubsMain {
+ dependsOn(commonMain)
+ }
+
+ jvmStubsMain {
+ dependsOn(commonStubsMain)
+ }
+ }
+}
+
+android {
+ namespace "androidx.compose.material3.adaptive.render.strategy"
+}
+
+androidx {
+ name = "androidx.compose.material3.adaptive:adaptive-render-strategy"
+ type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
+ inceptionYear = "2024"
+ description = "Material AdaptiveRenderStrategy library"
+}
diff --git a/compose/material3/adaptive/adaptive-render-strategy/src/commonMain/kotlin/androidx/compose/material3/adaptive/androidx-compose-material3-adaptive-adaptive-render-strategy-documentation.md b/compose/material3/adaptive/adaptive-render-strategy/src/commonMain/kotlin/androidx/compose/material3/adaptive/androidx-compose-material3-adaptive-adaptive-render-strategy-documentation.md
new file mode 100644
index 0000000..0fb75e6
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-render-strategy/src/commonMain/kotlin/androidx/compose/material3/adaptive/androidx-compose-material3-adaptive-adaptive-render-strategy-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+androidx.compose.material3.adaptive adaptive-render-strategy
+
+# Package androidx.compose.material3.adaptive.render.strategy
+
+Material AdaptiveRenderStrategy library
diff --git a/compose/material3/adaptive/adaptive/build.gradle b/compose/material3/adaptive/adaptive/build.gradle
index 7ab026e..1cb594e 100644
--- a/compose/material3/adaptive/adaptive/build.gradle
+++ b/compose/material3/adaptive/adaptive/build.gradle
@@ -43,7 +43,7 @@
implementation(libs.kotlinStdlib)
api("androidx.compose.foundation:foundation:1.6.5")
api("androidx.compose.ui:ui-geometry:1.6.5")
- api("androidx.window:window-core:1.3.0-rc01")
+ api("androidx.window:window-core:1.3.0")
}
}
@@ -63,7 +63,7 @@
dependencies {
api("androidx.annotation:annotation:1.8.1")
api("androidx.annotation:annotation-experimental:1.4.1")
- api("androidx.window:window:1.3.0-rc01")
+ api("androidx.window:window:1.3.0")
}
}
@@ -78,7 +78,7 @@
dependencies {
implementation(project(":compose:material3:material3"))
implementation(project(":compose:test-utils"))
- implementation(project(":window:window-testing"))
+ implementation("androidx.window:window-testing:1.3.0")
implementation(libs.junit)
implementation(libs.testRunner)
implementation(libs.truth)
diff --git a/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt b/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
deleted file mode 100644
index bf9047b..0000000
--- a/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2023 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.adaptive
-
-import android.app.Activity
-import android.content.Context
-import android.content.res.Configuration
-import android.graphics.Rect
-import androidx.annotation.UiContext
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.State
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.unit.IntSize
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.window.layout.WindowMetrics
-import androidx.window.layout.WindowMetricsCalculator
-import androidx.window.layout.WindowMetricsCalculatorDecorator
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CollectWindowSizeAsStateTest {
- @get:Rule val rule = createComposeRule()
-
- @Test
- fun test_collectWindowSizeAsState() {
- var actualWindowSize: IntSize = IntSize.Zero
-
- val mockWindowSize = mutableStateOf(MockWindowSize1)
- WindowMetricsCalculator.overrideDecorator(
- MockWindowMetricsCalculatorDecorator(mockWindowSize)
- )
-
- rule.setContent {
- val testConfiguration = Configuration(LocalConfiguration.current)
- testConfiguration.screenWidthDp = mockWindowSize.value.width
- testConfiguration.screenHeightDp = mockWindowSize.value.height
- CompositionLocalProvider(LocalConfiguration provides testConfiguration) {
- actualWindowSize = currentWindowSize()
- }
- }
-
- rule.runOnIdle { assertThat(actualWindowSize).isEqualTo(MockWindowSize1) }
-
- mockWindowSize.value = MockWindowSize2
-
- rule.runOnIdle { assertThat(actualWindowSize).isEqualTo(MockWindowSize2) }
- }
-
- companion object {
- val MockWindowSize1 = IntSize(1000, 600)
- val MockWindowSize2 = IntSize(800, 400)
- }
-}
-
-internal class MockWindowMetricsCalculatorDecorator(private val mockWindowSize: State<IntSize>) :
- WindowMetricsCalculatorDecorator {
- override fun decorate(calculator: WindowMetricsCalculator): WindowMetricsCalculator {
- return MockWindowMetricsCalculator(mockWindowSize)
- }
-}
-
-internal class MockWindowMetricsCalculator(private val mockWindowSize: State<IntSize>) :
- WindowMetricsCalculator {
- override fun computeCurrentWindowMetrics(activity: Activity): WindowMetrics {
- return WindowMetrics(
- Rect(0, 0, mockWindowSize.value.width, mockWindowSize.value.height),
- density = 1f
- )
- }
-
- override fun computeMaximumWindowMetrics(activity: Activity): WindowMetrics {
- return computeCurrentWindowMetrics(activity)
- }
-
- override fun computeCurrentWindowMetrics(@UiContext context: Context): WindowMetrics {
- return WindowMetrics(
- Rect(0, 0, mockWindowSize.value.width, mockWindowSize.value.height),
- density = 1f
- )
- }
-}
diff --git a/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CurrentWindowAdaptiveInfoTest.kt b/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CurrentWindowAdaptiveInfoTest.kt
deleted file mode 100644
index 8ef9012..0000000
--- a/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CurrentWindowAdaptiveInfoTest.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright 2023 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.adaptive
-
-import android.content.res.Configuration
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.toSize
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.window.core.layout.WindowSizeClass
-import androidx.window.layout.FoldingFeature
-import androidx.window.layout.WindowLayoutInfo
-import androidx.window.layout.WindowMetricsCalculator
-import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CurrentWindowAdaptiveInfoTest {
- private val composeRule = createComposeRule()
- private val layoutInfoRule = WindowLayoutInfoPublisherRule()
-
- @get:Rule val testRule: TestRule
-
- init {
- testRule = RuleChain.outerRule(layoutInfoRule).around(composeRule)
- }
-
- @Test
- fun test_currentWindowAdaptiveInfo() {
- lateinit var actualAdaptiveInfo: WindowAdaptiveInfo
- val mockWindowSize = mutableStateOf(MockWindowSize1)
- WindowMetricsCalculator.overrideDecorator(
- MockWindowMetricsCalculatorDecorator(mockWindowSize)
- )
-
- composeRule.setContent {
- val testConfiguration = Configuration(LocalConfiguration.current)
- testConfiguration.screenWidthDp = mockWindowSize.value.width
- testConfiguration.screenHeightDp = mockWindowSize.value.height
- CompositionLocalProvider(
- LocalDensity provides MockDensity,
- LocalConfiguration provides testConfiguration
- ) {
- actualAdaptiveInfo = currentWindowAdaptiveInfo()
- }
- }
-
- layoutInfoRule.overrideWindowLayoutInfo(WindowLayoutInfo(MockFoldingFeatures1))
-
- composeRule.runOnIdle {
- val mockSize = with(MockDensity) { MockWindowSize1.toSize().toDpSize() }
- assertThat(actualAdaptiveInfo.windowSizeClass)
- .isEqualTo(WindowSizeClass.compute(mockSize.width.value, mockSize.height.value))
- assertThat(actualAdaptiveInfo.windowPosture)
- .isEqualTo(calculatePosture(MockFoldingFeatures1))
- }
-
- layoutInfoRule.overrideWindowLayoutInfo(WindowLayoutInfo(MockFoldingFeatures2))
- mockWindowSize.value = MockWindowSize2
-
- composeRule.runOnIdle {
- val mockSize = with(MockDensity) { MockWindowSize2.toSize().toDpSize() }
- assertThat(actualAdaptiveInfo.windowSizeClass)
- .isEqualTo(WindowSizeClass.compute(mockSize.width.value, mockSize.height.value))
- assertThat(actualAdaptiveInfo.windowPosture)
- .isEqualTo(calculatePosture(MockFoldingFeatures2))
- }
- }
-
- companion object {
- private val MockFoldingFeatures1 =
- listOf(
- MockFoldingFeature(orientation = FoldingFeature.Orientation.HORIZONTAL),
- MockFoldingFeature(orientation = FoldingFeature.Orientation.VERTICAL),
- MockFoldingFeature(orientation = FoldingFeature.Orientation.HORIZONTAL)
- )
-
- private val MockFoldingFeatures2 =
- listOf(
- MockFoldingFeature(
- isSeparating = false,
- orientation = FoldingFeature.Orientation.HORIZONTAL,
- state = FoldingFeature.State.FLAT
- ),
- )
-
- private val MockWindowSize1 = IntSize(400, 800)
- private val MockWindowSize2 = IntSize(800, 400)
-
- private val MockDensity = Density(1f, 1f)
- }
-}
diff --git a/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowAdaptiveInfo.android.kt b/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowAdaptiveInfo.android.kt
index 9781752..f47f656 100644
--- a/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowAdaptiveInfo.android.kt
+++ b/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowAdaptiveInfo.android.kt
@@ -33,6 +33,7 @@
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
+@Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
actual fun currentWindowAdaptiveInfo(): WindowAdaptiveInfo {
val windowSize = with(LocalDensity.current) { currentWindowSize().toSize().toDpSize() }
return WindowAdaptiveInfo(
diff --git a/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
index 246ccc2..ffa2d48 100644
--- a/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
+++ b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
@@ -23,7 +23,7 @@
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.PaneScaffoldDirective
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
-import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -51,7 +51,7 @@
override fun toggleState() {}
@Composable
- fun ThreePaneScaffoldScope.TestPane(color: Color) {
+ fun ThreePaneScaffoldPaneScope.TestPane(color: Color) {
val content = @Composable { Box(modifier = Modifier.fillMaxSize().background(color)) }
if (animated) {
AnimatedPane(Modifier) { content() }
diff --git a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
index 77e13de..772978e 100644
--- a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
+++ b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
@@ -290,7 +290,7 @@
}
composable(listDetailRoute) {
val listScrollState = rememberScrollState()
- val selectedItem = scaffoldNavigator.currentDestination?.content
+ val selectedItem = scaffoldNavigator.currentDestination?.contentKey
// Back behavior can be customized based on the scaffold's layout.
// In this example, back navigation goes item-by-item when both
@@ -328,7 +328,7 @@
if (item != selectedItem) {
scaffoldNavigator.navigateTo(
pane = ListDetailPaneScaffoldRole.Detail,
- content = item,
+ contentKey = item,
)
}
}
diff --git a/compose/material3/material3-adaptive-navigation-suite/build.gradle b/compose/material3/material3-adaptive-navigation-suite/build.gradle
index 1c8f9ff..6d42780 100644
--- a/compose/material3/material3-adaptive-navigation-suite/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/build.gradle
@@ -44,7 +44,7 @@
api(project(":compose:material3:material3"))
api(project(":compose:material3:adaptive:adaptive"))
implementation("androidx.compose.ui:ui-util:1.6.0")
- implementation("androidx.window:window-core:1.3.0-beta02")
+ implementation("androidx.window:window-core:1.3.0")
}
}
@@ -77,7 +77,7 @@
dependsOn(commonTest)
dependencies {
implementation(project(":compose:test-utils"))
- implementation(project(":window:window-testing"))
+ implementation("androidx.window:window-testing:1.3.0")
implementation(libs.junit)
implementation(libs.testRunner)
implementation(libs.truth)
diff --git a/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle b/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle
index d65e6a8..57e571d 100644
--- a/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle
@@ -44,7 +44,7 @@
implementation(project(":compose:material3:material3-adaptive-navigation-suite"))
implementation("androidx.compose.ui:ui-util:1.6.0-rc01")
implementation("androidx.compose.ui:ui-tooling-preview:1.4.1")
- implementation(project(":window:window-core"))
+ implementation("androidx.window:window-core:1.3.0")
debugImplementation("androidx.compose.ui:ui-tooling:1.4.1")
}
diff --git a/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt b/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt
index df28658..7d112c9 100644
--- a/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("DEPRECATION") // Suppress for WindowWidthSizeClass
+
package androidx.compose.material3.adaptive.navigationsuite.samples
import androidx.annotation.Sampled
@@ -76,6 +78,7 @@
@Preview
@Sampled
@Composable
+@Suppress("DEPRECATION") // WindowWidthSizeClass is deprecated
fun NavigationSuiteScaffoldCustomConfigSample() {
var selectedItem by remember { mutableIntStateOf(0) }
val navItems = listOf("Songs", "Artists", "Playlists")
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt b/compose/material3/material3-adaptive-navigation-suite/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
index 1eb9fa1..a68b7ddc 100644
--- a/compose/material3/material3-adaptive-navigation-suite/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
@@ -30,6 +30,7 @@
class NavigationSuiteScaffoldTest {
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun navigationLayoutTypeTest_compactWidth_compactHeight() {
val mockAdaptiveInfo =
createMockAdaptiveInfo(windowSizeClass = WindowSizeClass.compute(400f, 400f))
@@ -39,6 +40,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun navigationLayoutTypeTest_compactWidth_mediumHeight() {
val mockAdaptiveInfo =
createMockAdaptiveInfo(windowSizeClass = WindowSizeClass.compute(400f, 800f))
@@ -48,6 +50,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun navigationLayoutTypeTest_compactWidth_expandedHeight() {
val mockAdaptiveInfo =
createMockAdaptiveInfo(windowSizeClass = WindowSizeClass.compute(400f, 1000f))
@@ -57,6 +60,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun navigationLayoutTypeTest_mediumWidth_compactHeight() {
val mockAdaptiveInfo =
createMockAdaptiveInfo(windowSizeClass = WindowSizeClass.compute(800f, 400f))
@@ -66,6 +70,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun navigationLayoutTypeTest_mediumWidth_mediumHeight() {
val mockAdaptiveInfo =
createMockAdaptiveInfo(windowSizeClass = WindowSizeClass.compute(800f, 800f))
@@ -75,6 +80,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun navigationLayoutTypeTest_mediumWidth_expandedHeight() {
val mockAdaptiveInfo =
createMockAdaptiveInfo(windowSizeClass = WindowSizeClass.compute(800f, 1000f))
@@ -84,6 +90,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun navigationLayoutTypeTest_expandedWidth_compactHeight() {
val mockAdaptiveInfo =
createMockAdaptiveInfo(windowSizeClass = WindowSizeClass.compute(1000f, 400f))
@@ -93,6 +100,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun navigationLayoutTypeTest_expandedWidth_mediumHeight() {
val mockAdaptiveInfo =
createMockAdaptiveInfo(windowSizeClass = WindowSizeClass.compute(1000f, 800f))
@@ -102,6 +110,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun navigationLayoutTypeTest_expandedWidth_expandedHeight() {
val mockAdaptiveInfo =
createMockAdaptiveInfo(windowSizeClass = WindowSizeClass.compute(1000f, 1000f))
@@ -111,6 +120,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun navigationLayoutTypeTest_tableTop() {
val mockAdaptiveInfo =
createMockAdaptiveInfo(
@@ -123,6 +133,7 @@
}
@Test
+ @Suppress("DEPRECATION") // WindowSizeClass#compute is deprecated
fun navigationLayoutTypeTest_tableTop_expandedWidth() {
val mockAdaptiveInfo =
createMockAdaptiveInfo(
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt b/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
index 5e96cd5..40758e0 100644
--- a/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("DEPRECATION") // Suppress for imports of WindowWidthSizeClass
+
package androidx.compose.material3.adaptive.navigationsuite
import androidx.compose.foundation.interaction.Interaction
@@ -407,6 +409,7 @@
* @param adaptiveInfo the provided [WindowAdaptiveInfo]
* @see NavigationSuiteScaffold
*/
+ @Suppress("DEPRECATION") // WindowWidthSizeClass deprecated
fun calculateFromAdaptiveInfo(adaptiveInfo: WindowAdaptiveInfo): NavigationSuiteType {
return with(adaptiveInfo) {
if (
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 887b901..f6366cc 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -994,16 +994,22 @@
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSquareShape();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonLocalContentColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonLocalContentColors();
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long largeContainerSize(optional int widthOption);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long mediumContainerSize(optional int widthOption);
method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke outlinedIconButtonBorder(boolean enabled);
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconButtonLocalContentColorBorder(boolean enabled);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonLocalContentColors();
method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconToggleButtonBorder(boolean enabled, boolean checked);
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconToggleButtonLocalContentColorBorder(boolean enabled, boolean checked);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonLocalContentColors();
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long smallContainerSize(optional int widthOption);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xLargeContainerSize(optional int widthOption);
@@ -1274,6 +1280,7 @@
}
public final class MaterialShapesKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public static androidx.compose.ui.graphics.Path toPath(androidx.graphics.shapes.Morph, float progress, optional androidx.compose.ui.graphics.Path path, optional int startAngle);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.Path toPath(androidx.graphics.shapes.RoundedPolygon, optional int startAngle);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.Shape toShape(androidx.graphics.shapes.RoundedPolygon, optional int startAngle);
}
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 887b901..f6366cc 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -994,16 +994,22 @@
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSquareShape();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonLocalContentColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonLocalContentColors();
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long largeContainerSize(optional int widthOption);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long mediumContainerSize(optional int widthOption);
method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke outlinedIconButtonBorder(boolean enabled);
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconButtonLocalContentColorBorder(boolean enabled);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonLocalContentColors();
method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconToggleButtonBorder(boolean enabled, boolean checked);
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
+ method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconToggleButtonLocalContentColorBorder(boolean enabled, boolean checked);
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonLocalContentColors();
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long smallContainerSize(optional int widthOption);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xLargeContainerSize(optional int widthOption);
@@ -1274,6 +1280,7 @@
}
public final class MaterialShapesKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public static androidx.compose.ui.graphics.Path toPath(androidx.graphics.shapes.Morph, float progress, optional androidx.compose.ui.graphics.Path path, optional int startAngle);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.Path toPath(androidx.graphics.shapes.RoundedPolygon, optional int startAngle);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.Shape toShape(androidx.graphics.shapes.RoundedPolygon, optional int startAngle);
}
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt
index 1a0178d..c4350f2 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt
@@ -21,6 +21,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -34,8 +35,10 @@
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.FilledIconToggleButton
+import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.FilledTonalIconToggleButton
import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion.Narrow
import androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion.Wide
@@ -321,10 +324,7 @@
// xsmall round icon button
OutlinedIconButton(
onClick = { /* doSomething() */ },
- modifier =
- Modifier
- // .minimumInteractiveComponentSize()
- .size(IconButtonDefaults.xSmallContainerSize()),
+ modifier = Modifier.size(IconButtonDefaults.xSmallContainerSize()),
shape = IconButtonDefaults.xSmallRoundShape
) {
Icon(
@@ -337,10 +337,7 @@
// Small round icon button
OutlinedIconButton(
onClick = { /* doSomething() */ },
- modifier =
- Modifier
- // .minimumInteractiveComponentSize()
- .size(IconButtonDefaults.smallContainerSize()),
+ modifier = Modifier.size(IconButtonDefaults.smallContainerSize()),
shape = IconButtonDefaults.smallRoundShape
) {
Icon(
@@ -487,11 +484,11 @@
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
-fun IconToggleButtonsDemo() {
+fun IconButtonAndToggleButtonsDemo() {
Column {
val rowScrollState = rememberScrollState()
val padding = 16.dp
- // unselected round row
+
Row(
modifier =
Modifier.height(150.dp)
@@ -506,7 +503,35 @@
Text("Outline")
Text("Standard")
}
- // unselected round row
+ // icon buttons
+ Row(
+ modifier =
+ Modifier.height(150.dp)
+ .horizontalScroll(rowScrollState)
+ .padding(horizontal = padding),
+ horizontalArrangement = Arrangement.spacedBy(padding),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Spacer(Modifier.width(76.dp))
+
+ FilledIconButton(onClick = {}) {
+ Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
+ }
+
+ FilledTonalIconButton(onClick = {}) {
+ Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
+ }
+
+ OutlinedIconButton(onClick = {}) {
+ Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
+ }
+
+ IconButton(onClick = {}) {
+ Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
+ }
+ }
+
+ // unselected icon toggle buttons
Row(
modifier =
Modifier.height(150.dp)
@@ -517,7 +542,15 @@
) {
var checked by remember { mutableStateOf(false) }
- Text("Unselected")
+ Text(
+ text =
+ if (!checked) {
+ "Unselected"
+ } else {
+ "Selected"
+ },
+ modifier = Modifier.defaultMinSize(minWidth = 76.dp),
+ )
FilledIconToggleButton(
checked = checked,
@@ -572,7 +605,7 @@
}
}
- // unselected round row
+ // selected icon toggle buttons
Row(
modifier =
Modifier.height(150.dp)
@@ -582,7 +615,17 @@
verticalAlignment = Alignment.CenterVertically
) {
var checked by remember { mutableStateOf(true) }
- Text("Selected")
+
+ Text(
+ text =
+ if (!checked) {
+ "Unselected"
+ } else {
+ "Selected"
+ },
+ modifier = Modifier.defaultMinSize(minWidth = 76.dp)
+ )
+
FilledIconToggleButton(
checked = checked,
onCheckedChange = { checked = it },
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt
index 66e8991..a84867f 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt
@@ -34,14 +34,17 @@
listOf(
ComposableDemo("Sizes") { IconButtonMeasurementsDemo() },
ComposableDemo("Corners") { IconButtonCornerRadiusDemo() },
- ComposableDemo("Icon toggle buttons") { IconToggleButtonsDemo() },
+ ComposableDemo("Icon button & icon toggle buttons") {
+ IconButtonAndToggleButtonsDemo()
+ },
)
),
DemoCategory(
"Shapes",
listOf(
ComposableDemo("Shape") { ShapeDemo() },
- ComposableDemo("Material Shapes") { MaterialShapeDemo() },
+ ComposableDemo("Material Shape") { MaterialShapeDemo() },
+ ComposableDemo("Material Shape Morphing") { MaterialShapeMorphDemo() },
)
)
),
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt
index 49462ec..372e058 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt
@@ -16,10 +16,13 @@
package androidx.compose.material3.demos
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.border
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -30,6 +33,7 @@
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccessAlarms
import androidx.compose.material.icons.outlined.AccessibilityNew
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@@ -40,16 +44,24 @@
import androidx.compose.material3.toPath
import androidx.compose.material3.toShape
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.graphics.shapes.Morph
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -135,3 +147,47 @@
}
}
}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun MaterialShapeMorphDemo() {
+ val morph = remember { Morph(MaterialShapes.Circle, MaterialShapes.Cookie9Sided) }
+ val interactionSource = remember { MutableInteractionSource() }
+ val isPressed by interactionSource.collectIsPressedAsState()
+ val animatedProgress =
+ animateFloatAsState(
+ targetValue = if (isPressed) 1f else 0f,
+ label = "progress",
+ animationSpec = MaterialTheme.motionScheme.defaultSpatialSpec()
+ )
+ val morphShape = remember {
+ object : Shape {
+ private val path = Path()
+ private val matrix = Matrix()
+
+ override fun createOutline(
+ size: Size,
+ layoutDirection: LayoutDirection,
+ density: Density
+ ): Outline {
+ matrix.reset()
+ matrix.scale(size.width, size.height)
+ morph.toPath(animatedProgress.value, path)
+ path.transform(matrix)
+ return Outline.Generic(path)
+ }
+ }
+ }
+ Button(
+ onClick = { /* on-click*/ },
+ modifier = Modifier.requiredSize(48.dp),
+ shape = morphShape,
+ interactionSource = interactionSource
+ ) {
+ Icon(
+ Icons.Outlined.AccessAlarms,
+ modifier = Modifier.requiredSize(24.dp),
+ contentDescription = "Localized description",
+ )
+ }
+}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
index a3c073d..14ccfa7 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
@@ -49,7 +49,7 @@
@Composable
fun IconButtonSample() {
IconButton(onClick = { /* doSomething() */ }) {
- Icon(Icons.Outlined.Lock, contentDescription = "Localized description")
+ Icon(Icons.Filled.Lock, contentDescription = "Localized description")
}
}
@@ -71,7 +71,7 @@
shape = IconButtonDefaults.xSmallSquareShape
) {
Icon(
- Icons.Outlined.Lock,
+ Icons.Filled.Lock,
contentDescription = "Localized description",
modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
)
@@ -94,7 +94,7 @@
shape = IconButtonDefaults.mediumRoundShape
) {
Icon(
- Icons.Outlined.Lock,
+ Icons.Filled.Lock,
contentDescription = "Localized description",
modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
)
@@ -112,7 +112,7 @@
shape = IconButtonDefaults.largeRoundShape
) {
Icon(
- Icons.Outlined.Lock,
+ Icons.Filled.Lock,
contentDescription = "Localized description",
modifier = Modifier.size(IconButtonDefaults.largeIconSize)
)
@@ -125,7 +125,7 @@
fun TintedIconButtonSample() {
IconButton(onClick = { /* doSomething() */ }) {
Icon(
- rememberVectorPainter(image = Icons.Outlined.Lock),
+ rememberVectorPainter(image = Icons.Filled.Lock),
contentDescription = "Localized description",
tint = Color.Red
)
@@ -165,7 +165,7 @@
@Composable
fun FilledIconButtonSample() {
FilledIconButton(onClick = { /* doSomething() */ }) {
- Icon(Icons.Outlined.Lock, contentDescription = "Localized description")
+ Icon(Icons.Filled.Lock, contentDescription = "Localized description")
}
}
@@ -212,7 +212,7 @@
@Composable
fun FilledTonalIconButtonSample() {
FilledTonalIconButton(onClick = { /* doSomething() */ }) {
- Icon(Icons.Outlined.Lock, contentDescription = "Localized description")
+ Icon(Icons.Filled.Lock, contentDescription = "Localized description")
}
}
@@ -259,7 +259,7 @@
@Composable
fun OutlinedIconButtonSample() {
OutlinedIconButton(onClick = { /* doSomething() */ }) {
- Icon(Icons.Outlined.Lock, contentDescription = "Localized description")
+ Icon(Icons.Filled.Lock, contentDescription = "Localized description")
}
}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SplitButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SplitButtonSamples.kt
index a2e2d24..3342355 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SplitButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SplitButtonSamples.kt
@@ -21,8 +21,8 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Edit
-import androidx.compose.material.icons.outlined.KeyboardArrowDown
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ElevatedSplitButton
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@@ -58,7 +58,7 @@
onClick = { /* Do Nothing */ },
) {
Icon(
- Icons.Outlined.Edit,
+ Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
@@ -82,7 +82,7 @@
label = "Trailing Icon Rotation"
)
Icon(
- Icons.Outlined.KeyboardArrowDown,
+ Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
@@ -107,7 +107,7 @@
onTrailingButtonClick = { checked = !checked },
leadingContent = {
Icon(
- Icons.Outlined.Edit,
+ Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description"
)
@@ -121,7 +121,7 @@
label = "Trailing Icon Rotation"
)
Icon(
- Icons.Outlined.KeyboardArrowDown,
+ Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
@@ -145,7 +145,7 @@
onTrailingButtonClick = { checked = !checked },
leadingContent = {
Icon(
- Icons.Outlined.Edit,
+ Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description"
)
@@ -159,7 +159,7 @@
label = "Trailing Icon Rotation"
)
Icon(
- Icons.Outlined.KeyboardArrowDown,
+ Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
@@ -183,7 +183,7 @@
onTrailingButtonClick = { checked = !checked },
leadingContent = {
Icon(
- Icons.Outlined.Edit,
+ Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description"
)
@@ -197,7 +197,7 @@
label = "Trailing Icon Rotation"
)
Icon(
- Icons.Outlined.KeyboardArrowDown,
+ Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
@@ -221,7 +221,7 @@
onTrailingButtonClick = { checked = !checked },
leadingContent = {
Icon(
- Icons.Outlined.Edit,
+ Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description"
)
@@ -235,7 +235,7 @@
label = "Trailing Icon Rotation"
)
Icon(
- Icons.Outlined.KeyboardArrowDown,
+ Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
@@ -272,7 +272,7 @@
label = "Trailing Icon Rotation"
)
Icon(
- Icons.Outlined.KeyboardArrowDown,
+ Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
@@ -297,7 +297,7 @@
onClick = { /* Do Nothing */ },
) {
Icon(
- Icons.Outlined.Edit,
+ Icons.Filled.Edit,
contentDescription = "Localized description",
Modifier.size(SplitButtonDefaults.LeadingIconSize)
)
@@ -314,7 +314,7 @@
label = "Trailing Icon Rotation"
)
Icon(
- Icons.Outlined.KeyboardArrowDown,
+ Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AlertDialogTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AlertDialogTest.kt
index 0c8bf40..0bd5601 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AlertDialogTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AlertDialogTest.kt
@@ -24,7 +24,6 @@
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
-import androidx.compose.material3.tokens.TextButtonTokens
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -91,7 +90,9 @@
TextButton(onClick = { /* doSomething() */ }) {
Text("Confirm")
buttonContentColor = LocalContentColor.current
- expectedButtonContentColor = TextButtonTokens.LabelColor.value
+ // TODO change this back to the TextButtonTokens.LabelColor once the tokens
+ // are updated
+ expectedButtonContentColor = MaterialTheme.colorScheme.primary
}
},
containerColor = Color.Yellow,
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonTest.kt
index e7f9bd2..9b52bf3 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonTest.kt
@@ -415,7 +415,9 @@
.isEqualTo(
ButtonColors(
containerColor = Color.Transparent,
- contentColor = TextButtonTokens.LabelColor.value,
+ // TODO change this back to the TextButtonTokens.LabelColor once the tokens
+ // are updated
+ contentColor = MaterialTheme.colorScheme.primary,
disabledContainerColor = Color.Transparent,
disabledContentColor =
TextButtonTokens.DisabledLabelColor.value.copy(
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
index 7a64ed6..1cf7f4b 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
@@ -32,9 +32,9 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.tokens.ExtendedFabPrimaryTokens
-import androidx.compose.material3.tokens.FabPrimaryLargeTokens
-import androidx.compose.material3.tokens.FabPrimarySmallTokens
-import androidx.compose.material3.tokens.FabPrimaryTokens
+import androidx.compose.material3.tokens.FabBaselineTokens
+import androidx.compose.material3.tokens.FabLargeTokens
+import androidx.compose.material3.tokens.FabSmallTokens
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
@@ -128,12 +128,12 @@
Icon(Icons.Filled.Favorite, null, modifier = Modifier.testTag("icon"))
}
}
- .assertIsSquareWithSize(FabPrimaryTokens.ContainerHeight)
+ .assertIsSquareWithSize(FabBaselineTokens.ContainerHeight)
rule
.onNodeWithTag("icon", useUnmergedTree = true)
- .assertHeightIsEqualTo(FabPrimaryTokens.IconSize)
- .assertWidthIsEqualTo(FabPrimaryTokens.IconSize)
+ .assertHeightIsEqualTo(FabBaselineTokens.IconSize)
+ .assertWidthIsEqualTo(FabBaselineTokens.IconSize)
}
@OptIn(ExperimentalMaterial3Api::class)
@@ -148,12 +148,12 @@
}
}
// Expecting the size to be equal to the token size.
- .assertIsSquareWithSize(FabPrimarySmallTokens.ContainerHeight)
+ .assertIsSquareWithSize(FabSmallTokens.ContainerHeight)
rule
.onNodeWithTag("icon", useUnmergedTree = true)
- .assertHeightIsEqualTo(FabPrimarySmallTokens.IconSize)
- .assertWidthIsEqualTo(FabPrimarySmallTokens.IconSize)
+ .assertHeightIsEqualTo(FabSmallTokens.IconSize)
+ .assertWidthIsEqualTo(FabSmallTokens.IconSize)
}
@OptIn(ExperimentalMaterial3Api::class)
@@ -184,7 +184,7 @@
)
}
}
- .assertIsSquareWithSize(FabPrimaryLargeTokens.ContainerHeight)
+ .assertIsSquareWithSize(FabLargeTokens.ContainerHeight)
rule
.onNodeWithTag("icon", useUnmergedTree = true)
@@ -206,7 +206,7 @@
rule
.onNodeWithTag("FAB")
.assertHeightIsEqualTo(ExtendedFabPrimaryTokens.ContainerHeight)
- .assertWidthIsAtLeast(FabPrimaryTokens.ContainerHeight)
+ .assertWidthIsAtLeast(FabBaselineTokens.ContainerHeight)
}
@Test
@@ -468,7 +468,7 @@
)
}
- rule.onNodeWithTag("FAB").assertIsSquareWithSize(FabPrimaryTokens.ContainerHeight)
+ rule.onNodeWithTag("FAB").assertIsSquareWithSize(FabBaselineTokens.ContainerHeight)
rule
.onNodeWithTag("icon", useUnmergedTree = true)
@@ -504,9 +504,9 @@
rule
.onNodeWithTag("FAB")
- .assertIsSquareWithSize(FabPrimaryTokens.ContainerHeight)
- .assertHeightIsEqualTo(FabPrimaryTokens.ContainerHeight)
- .assertWidthIsEqualTo(FabPrimaryTokens.ContainerWidth)
+ .assertIsSquareWithSize(FabBaselineTokens.ContainerHeight)
+ .assertHeightIsEqualTo(FabBaselineTokens.ContainerHeight)
+ .assertWidthIsEqualTo(FabBaselineTokens.ContainerWidth)
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
index 647b1cf..5eaa44a 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
@@ -306,10 +306,10 @@
}
@Test
- fun iconButton_defaultColors() {
+ fun iconButton_defaultLocalContentColors() {
rule.setMaterialContent(lightColorScheme()) {
CompositionLocalProvider(LocalContentColor provides Color.Blue) {
- Truth.assertThat(IconButtonDefaults.iconButtonColors())
+ Truth.assertThat(IconButtonDefaults.iconButtonLocalContentColors())
.isEqualTo(
IconButtonColors(
containerColor = Color.Transparent,
@@ -324,15 +324,35 @@
}
@Test
+ fun iconButton_defaultColors() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Truth.assertThat(IconButtonDefaults.iconButtonColors())
+ .isEqualTo(
+ IconButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = StandardIconButtonTokens.Color.value,
+ disabledContainerColor = Color.Transparent,
+ disabledContentColor =
+ StandardIconButtonTokens.DisabledColor.value.copy(
+ alpha = StandardIconButtonTokens.DisabledOpacity
+ )
+ )
+ )
+ }
+ }
+
+ @Test
fun iconButtonColors_localContentColor() {
rule.setMaterialContent(lightColorScheme()) {
CompositionLocalProvider(LocalContentColor provides Color.Blue) {
- val colors = IconButtonDefaults.iconButtonColors()
+ val colors = IconButtonDefaults.iconButtonLocalContentColors()
assert(colors.contentColor == Color.Blue)
}
CompositionLocalProvider(LocalContentColor provides Color.Red) {
- val colors = IconButtonDefaults.iconButtonColors(containerColor = Color.Green)
+ val colors =
+ IconButtonDefaults.iconButtonLocalContentColors()
+ .copy(containerColor = Color.Green)
assert(colors.containerColor == Color.Green)
assert(colors.contentColor == Color.Red)
}
@@ -340,10 +360,10 @@
}
@Test
- fun iconButtonColors_customValues() {
+ fun iconButtonColors_customValues_useLocalContentColor() {
rule.setMaterialContent(lightColorScheme()) {
CompositionLocalProvider(LocalContentColor provides Color.Blue) {
- val colors = IconButtonDefaults.iconButtonColors()
+ val colors = IconButtonDefaults.iconButtonLocalContentColors()
assert(colors.contentColor == Color.Blue)
assert(
colors.disabledContentColor ==
@@ -368,6 +388,25 @@
}
@Test
+ fun iconButtonColors_customValues() {
+ rule.setMaterialContent(lightColorScheme()) {
+ CompositionLocalProvider(LocalContentColor provides Color.Red) {
+ val colors =
+ IconButtonDefaults.iconButtonColors(
+ containerColor = Color.Blue,
+ contentColor = Color.Green
+ )
+ assert(colors.containerColor == Color.Blue)
+ assert(colors.contentColor == Color.Green)
+ assert(
+ colors.disabledContentColor ==
+ Color.Green.copy(StandardIconButtonTokens.DisabledOpacity)
+ )
+ }
+ }
+ }
+
+ @Test
fun iconButtonColors_copy() {
rule.setMaterialContent(lightColorScheme()) {
val colors = IconButtonDefaults.iconButtonColors().copy()
@@ -501,10 +540,10 @@
}
@Test
- fun iconToggleButton_defaultColors() {
+ fun iconToggleButton_defaultLocalContentColors() {
rule.setMaterialContent(lightColorScheme()) {
val localContentColor = LocalContentColor.current
- Truth.assertThat(IconButtonDefaults.iconToggleButtonColors())
+ Truth.assertThat(IconButtonDefaults.iconToggleButtonLocalContentColors())
.isEqualTo(
IconToggleButtonColors(
containerColor = Color.Transparent,
@@ -522,6 +561,26 @@
}
@Test
+ fun iconToggleButton_defaultColors() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Truth.assertThat(IconButtonDefaults.iconToggleButtonColors())
+ .isEqualTo(
+ IconToggleButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = StandardIconButtonTokens.UnselectedColor.value,
+ disabledContainerColor = Color.Transparent,
+ disabledContentColor =
+ StandardIconButtonTokens.DisabledColor.value.copy(
+ alpha = StandardIconButtonTokens.DisabledOpacity
+ ),
+ checkedContainerColor = Color.Transparent,
+ checkedContentColor = StandardIconButtonTokens.SelectedColor.value
+ )
+ )
+ }
+ }
+
+ @Test
fun filledIconButton_xsmall_visualBounds() {
val expectedWidth =
with(rule.density) { IconButtonDefaults.xSmallContainerSize().width.roundToPx() }
@@ -1136,23 +1195,6 @@
}
@Test
- fun outlinedIconButton_defaultColors() {
- rule.setMaterialContent(lightColorScheme()) {
- val localContentColor = LocalContentColor.current
- Truth.assertThat(IconButtonDefaults.outlinedIconButtonColors())
- .isEqualTo(
- IconButtonColors(
- containerColor = Color.Transparent,
- contentColor = localContentColor,
- disabledContainerColor = Color.Transparent,
- disabledContentColor =
- localContentColor.copy(alpha = OutlinedIconButtonTokens.DisabledOpacity)
- )
- )
- }
- }
-
- @Test
fun outlinedIconToggleButton_size() {
rule
.setMaterialContentForSizeAssertions {
@@ -1260,13 +1302,74 @@
}
@Test
- fun outlinedIconToggleButton_defaultColors() {
+ fun outlinedIconButton_defaultLocalContentColors() {
rule.setMaterialContent(lightColorScheme()) {
val localContentColor = LocalContentColor.current
+ Truth.assertThat(IconButtonDefaults.outlinedIconButtonLocalContentColors())
+ .isEqualTo(
+ IconButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = localContentColor,
+ disabledContainerColor = Color.Transparent,
+ disabledContentColor =
+ localContentColor.copy(alpha = OutlinedIconButtonTokens.DisabledOpacity)
+ )
+ )
+ }
+ }
+
+ @Test
+ fun outlinedIconButton_defaultColors() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Truth.assertThat(IconButtonDefaults.outlinedIconButtonColors())
+ .isEqualTo(
+ IconButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = OutlinedIconButtonTokens.Color.value,
+ disabledContainerColor = Color.Transparent,
+ disabledContentColor =
+ OutlinedIconButtonTokens.DisabledColor.value.copy(
+ alpha = OutlinedIconButtonTokens.DisabledOpacity
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ fun outlinedIconToggleButton_defaultColors() {
+ rule.setMaterialContent(lightColorScheme()) {
Truth.assertThat(IconButtonDefaults.outlinedIconToggleButtonColors())
.isEqualTo(
IconToggleButtonColors(
containerColor = Color.Transparent,
+ contentColor = OutlinedIconButtonTokens.UnselectedColor.value,
+ disabledContainerColor = Color.Transparent,
+ disabledContentColor =
+ OutlinedIconButtonTokens.DisabledColor.value.copy(
+ alpha = OutlinedIconButtonTokens.DisabledOpacity
+ ),
+ checkedContainerColor =
+ OutlinedIconButtonTokens.SelectedContainerColor.value,
+ checkedContentColor = OutlinedIconButtonTokens.SelectedColor.value
+ )
+ )
+ }
+ }
+
+ @Test
+ fun outlinedIconToggleButton_defaultLocalContentColors_reuse() {
+ rule.setMaterialContent(lightColorScheme()) {
+ val localContentColor = LocalContentColor.current
+ Truth.assertThat(MaterialTheme.colorScheme.defaultOutlinedIconToggleButtonColorsCached)
+ .isNull()
+ val colors = IconButtonDefaults.outlinedIconToggleButtonLocalContentColors()
+ Truth.assertThat(MaterialTheme.colorScheme.defaultOutlinedIconToggleButtonColorsCached)
+ .isNotNull()
+ Truth.assertThat(colors)
+ .isEqualTo(
+ IconToggleButtonColors(
+ containerColor = Color.Transparent,
contentColor = localContentColor,
disabledContainerColor = Color.Transparent,
disabledContentColor =
@@ -1295,7 +1398,7 @@
.isEqualTo(
IconToggleButtonColors(
containerColor = Color.Transparent,
- contentColor = localContentColor,
+ contentColor = OutlinedIconButtonTokens.UnselectedColor.value,
disabledContainerColor = Color.Transparent,
disabledContentColor =
localContentColor.copy(
@@ -1303,8 +1406,7 @@
),
checkedContainerColor =
OutlinedIconButtonTokens.SelectedContainerColor.value,
- checkedContentColor =
- contentColorFor(OutlinedIconButtonTokens.SelectedContainerColor.value)
+ checkedContentColor = OutlinedIconButtonTokens.SelectedColor.value
)
)
}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/MaterialShapesScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/MaterialShapesScreenshotTest.kt
index 5a2855b..bc28a4f 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/MaterialShapesScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/MaterialShapesScreenshotTest.kt
@@ -26,15 +26,24 @@
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
import androidx.compose.testutils.assertAgainstGolden
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.graphics.shapes.Morph
import androidx.graphics.shapes.RoundedPolygon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
@@ -48,7 +57,7 @@
@MediumTest
@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-class MaterialShapesScreenshotTest() {
+class MaterialShapesScreenshotTest {
@get:Rule val rule = createComposeRule()
@get:Rule val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3)
@@ -57,6 +66,54 @@
private val wrapperTestTag = "materialShapesWrapper"
@Test
+ fun morphShape_start() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(wrap.testTag(wrapperTestTag)) {
+ Button(
+ onClick = {},
+ modifier = Modifier.requiredSize(56.dp),
+ shape = morphShape(progress = 0f)
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ }
+ }
+ assertIndicatorAgainstGolden("morphShape_start")
+ }
+
+ @Test
+ fun morphShape_mid() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(wrap.testTag(wrapperTestTag)) {
+ Button(
+ onClick = {},
+ modifier = Modifier.requiredSize(56.dp),
+ shape = morphShape(progress = 0.5f)
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ }
+ }
+ assertIndicatorAgainstGolden("morphShape_mid")
+ }
+
+ @Test
+ fun morphShape_end() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(wrap.testTag(wrapperTestTag)) {
+ Button(
+ onClick = {},
+ modifier = Modifier.requiredSize(56.dp),
+ shape = morphShape(progress = 1f)
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ }
+ }
+ assertIndicatorAgainstGolden("morphShape_end")
+ }
+
+ @Test
fun materialShapes_allShapes() {
rule.setMaterialContent(lightColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
@@ -121,6 +178,23 @@
)
}
+ private fun morphShape(progress: Float): Shape {
+ val morph = Morph(MaterialShapes.Diamond, MaterialShapes.Cookie12Sided)
+ return object : Shape {
+ override fun createOutline(
+ size: Size,
+ layoutDirection: LayoutDirection,
+ density: Density
+ ): Outline {
+ val matrix = Matrix()
+ matrix.scale(size.width, size.height)
+ val path = morph.toPath(progress)
+ path.transform(matrix)
+ return Outline.Generic(path)
+ }
+ }
+ }
+
private fun assertIndicatorAgainstGolden(goldenName: String) {
rule
.onNodeWithTag(wrapperTestTag)
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/anchoredDraggable/AnchoredDraggableStateTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/anchoredDraggable/AnchoredDraggableStateTest.kt
index bf7443d..2ca56a9 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/anchoredDraggable/AnchoredDraggableStateTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/anchoredDraggable/AnchoredDraggableStateTest.kt
@@ -697,7 +697,7 @@
initialValue = A,
defaultPositionalThreshold,
defaultVelocityThreshold,
- animationSpec = defaultAnimationSpec
+ animationSpec = defaultAnimationSpec,
)
anchoredDraggableState.updateAnchors(
DraggableAnchors {
@@ -735,6 +735,40 @@
dragJob.cancel()
}
+ @Test
+ fun anchoredDraggable_anchoredDrag_doesNotUpdateOnConfirmValueChange() = runTest {
+ val anchoredDraggableState =
+ AnchoredDraggableState(
+ initialValue = B,
+ defaultPositionalThreshold,
+ defaultVelocityThreshold,
+ animationSpec = defaultAnimationSpec,
+ confirmValueChange = { false }
+ )
+ anchoredDraggableState.updateAnchors(
+ DraggableAnchors {
+ A at 0f
+ B at 200f
+ }
+ )
+
+ assertThat(anchoredDraggableState.targetValue).isEqualTo(B)
+
+ val unexpectedTarget = A
+ val targetUpdates = Channel<Float>()
+ val dragJob =
+ launch(Dispatchers.Unconfined) {
+ anchoredDraggableState.anchoredDrag(unexpectedTarget) { anchors, latestTarget ->
+ targetUpdates.send(anchors.positionOf(latestTarget))
+ suspendIndefinitely()
+ }
+ }
+
+ val firstTarget = targetUpdates.receive()
+ assertThat(firstTarget).isEqualTo(200f)
+ dragJob.cancel()
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun anchoredDraggable_dragCompletesExceptionally_cleansUp() = runTest {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index f712ed9..9193761 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -45,7 +45,7 @@
import androidx.compose.material3.internal.ProvideContentColorTextStyle
import androidx.compose.material3.internal.systemBarsForVisualComponents
import androidx.compose.material3.tokens.BottomAppBarTokens
-import androidx.compose.material3.tokens.FabSecondaryTokens
+import androidx.compose.material3.tokens.FabSecondaryContainerTokens
import androidx.compose.material3.tokens.MotionSchemeKeyTokens
import androidx.compose.material3.tokens.TopAppBarLargeTokens
import androidx.compose.material3.tokens.TopAppBarMediumTokens
@@ -1971,7 +1971,7 @@
/** The color of a [BottomAppBar]'s [FloatingActionButton] */
val bottomAppBarFabColor: Color
- @Composable get() = FabSecondaryTokens.ContainerColor.value
+ @Composable get() = FabSecondaryContainerTokens.ContainerColor.value
val HorizontalArrangement =
Arrangement.spacedBy(32.dp, Alignment.CenterHorizontally) // TODO tokens
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
index 8ca5c0f..1473429 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
@@ -35,6 +35,7 @@
import androidx.compose.material3.internal.animateElevation
import androidx.compose.material3.tokens.BaselineButtonTokens
import androidx.compose.material3.tokens.ButtonSmallTokens
+import androidx.compose.material3.tokens.ColorSchemeKeyTokens
import androidx.compose.material3.tokens.ElevatedButtonTokens
import androidx.compose.material3.tokens.FilledButtonTokens
import androidx.compose.material3.tokens.FilledTonalButtonTokens
@@ -805,7 +806,8 @@
return defaultTextButtonColorsCached
?: ButtonColors(
containerColor = Color.Transparent,
- contentColor = fromToken(TextButtonTokens.LabelColor),
+ // TODO replace with the token value once it's corrected
+ contentColor = fromToken(ColorSchemeKeyTokens.Primary),
disabledContainerColor = Color.Transparent,
disabledContentColor =
fromToken(TextButtonTokens.DisabledLabelColor)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
index e3f0cc8..e4208ec 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
@@ -43,10 +43,16 @@
import androidx.compose.foundation.layout.width
import androidx.compose.material3.internal.ProvideContentColorTextStyle
import androidx.compose.material3.internal.animateElevation
+import androidx.compose.material3.tokens.ElevationTokens
+import androidx.compose.material3.tokens.ExtendedFabLargeTokens
+import androidx.compose.material3.tokens.ExtendedFabMediumTokens
import androidx.compose.material3.tokens.ExtendedFabPrimaryTokens
-import androidx.compose.material3.tokens.FabPrimaryLargeTokens
-import androidx.compose.material3.tokens.FabPrimarySmallTokens
-import androidx.compose.material3.tokens.FabPrimaryTokens
+import androidx.compose.material3.tokens.ExtendedFabSmallTokens
+import androidx.compose.material3.tokens.FabBaselineTokens
+import androidx.compose.material3.tokens.FabLargeTokens
+import androidx.compose.material3.tokens.FabMediumTokens
+import androidx.compose.material3.tokens.FabPrimaryContainerTokens
+import androidx.compose.material3.tokens.FabSmallTokens
import androidx.compose.material3.tokens.MotionSchemeKeyTokens
import androidx.compose.material3.tokens.TypographyKeyTokens
import androidx.compose.runtime.Composable
@@ -130,8 +136,8 @@
FloatingActionButton(
onClick,
ExtendedFabPrimaryTokens.LabelTextFont.value,
- FabPrimaryTokens.ContainerWidth,
- FabPrimaryTokens.ContainerHeight,
+ FabBaselineTokens.ContainerWidth,
+ FabBaselineTokens.ContainerHeight,
modifier,
shape,
containerColor,
@@ -229,8 +235,8 @@
onClick = onClick,
modifier =
modifier.sizeIn(
- minWidth = FabPrimarySmallTokens.ContainerWidth,
- minHeight = FabPrimarySmallTokens.ContainerHeight,
+ minWidth = FabSmallTokens.ContainerWidth,
+ minHeight = FabSmallTokens.ContainerHeight,
),
shape = shape,
containerColor = containerColor,
@@ -285,10 +291,9 @@
FloatingActionButton(
onClick = onClick,
modifier =
- // TODO: update sizes to use tokens
modifier.sizeIn(
- minWidth = 80.dp,
- minHeight = 80.dp,
+ minWidth = FabMediumTokens.ContainerWidth,
+ minHeight = FabMediumTokens.ContainerHeight,
),
shape = shape,
containerColor = containerColor,
@@ -346,8 +351,8 @@
onClick = onClick,
modifier =
modifier.sizeIn(
- minWidth = FabPrimaryLargeTokens.ContainerWidth,
- minHeight = FabPrimaryLargeTokens.ContainerHeight,
+ minWidth = FabLargeTokens.ContainerWidth,
+ minHeight = FabLargeTokens.ContainerHeight,
),
shape = shape,
containerColor = containerColor,
@@ -412,7 +417,11 @@
interactionSource = interactionSource,
) {
Row(
- modifier = Modifier.padding(horizontal = SmallExtendedFabHorizontalPadding),
+ modifier =
+ Modifier.padding(
+ start = SmallExtendedFabPaddingStart,
+ end = SmallExtendedFabPaddingEnd
+ ),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
content = content,
@@ -474,7 +483,11 @@
interactionSource = interactionSource,
) {
Row(
- modifier = Modifier.padding(horizontal = MediumExtendedFabHorizontalPadding),
+ modifier =
+ Modifier.padding(
+ start = MediumExtendedFabPaddingStart,
+ end = MediumExtendedFabPaddingEnd
+ ),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
content = content,
@@ -536,7 +549,11 @@
interactionSource = interactionSource,
) {
Row(
- modifier = Modifier.padding(horizontal = LargeExtendedFabHorizontalPadding),
+ modifier =
+ Modifier.padding(
+ start = LargeExtendedFabPaddingStart,
+ end = LargeExtendedFabPaddingEnd
+ ),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
content = content,
@@ -662,7 +679,8 @@
textStyle = SmallExtendedFabTextStyle.value,
minWidth = SmallExtendedFabMinimumWidth,
minHeight = SmallExtendedFabMinimumHeight,
- horizontalPadding = SmallExtendedFabHorizontalPadding,
+ startPadding = SmallExtendedFabPaddingStart,
+ endPadding = SmallExtendedFabPaddingEnd,
iconPadding = SmallExtendedFabIconPadding,
modifier = modifier,
expanded = expanded,
@@ -729,7 +747,8 @@
textStyle = MediumExtendedFabTextStyle.value,
minWidth = MediumExtendedFabMinimumWidth,
minHeight = MediumExtendedFabMinimumHeight,
- horizontalPadding = MediumExtendedFabHorizontalPadding,
+ startPadding = MediumExtendedFabPaddingStart,
+ endPadding = MediumExtendedFabPaddingEnd,
iconPadding = MediumExtendedFabIconPadding,
modifier = modifier,
expanded = expanded,
@@ -796,7 +815,8 @@
textStyle = LargeExtendedFabTextStyle.value,
minWidth = LargeExtendedFabMinimumWidth,
minHeight = LargeExtendedFabMinimumHeight,
- horizontalPadding = LargeExtendedFabHorizontalPadding,
+ startPadding = LargeExtendedFabPaddingStart,
+ endPadding = LargeExtendedFabPaddingEnd,
iconPadding = LargeExtendedFabIconPadding,
modifier = modifier,
expanded = expanded,
@@ -877,7 +897,7 @@
if (expanded) {
ExtendedFabMinimumWidth
} else {
- FabPrimaryTokens.ContainerWidth
+ FabBaselineTokens.ContainerWidth
}
)
.padding(start = startPadding, end = endPadding),
@@ -907,7 +927,8 @@
textStyle: TextStyle,
minWidth: Dp,
minHeight: Dp,
- horizontalPadding: Dp,
+ startPadding: Dp,
+ endPadding: Dp,
iconPadding: Dp,
modifier: Modifier = Modifier,
expanded: Boolean = true,
@@ -947,7 +968,7 @@
layout(width, placeable.height) { placeable.place(0, 0) }
}
.sizeIn(minWidth = minWidth, minHeight = minHeight)
- .padding(horizontal = horizontalPadding),
+ .padding(start = startPadding, end = endPadding),
verticalAlignment = Alignment.CenterVertically,
) {
icon()
@@ -978,18 +999,18 @@
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
@get:ExperimentalMaterial3ExpressiveApi
@ExperimentalMaterial3ExpressiveApi
- val MediumIconSize = 28.dp // TODO: update to use token
+ val MediumIconSize = FabMediumTokens.IconSize
/** The recommended size of the icon inside a [LargeFloatingActionButton]. */
- val LargeIconSize = FabPrimaryLargeTokens.IconSize
+ val LargeIconSize = 36.dp // TODO: FabLargeTokens.IconSize is incorrect
/** Default shape for a floating action button. */
val shape: Shape
- @Composable get() = FabPrimaryTokens.ContainerShape.value
+ @Composable get() = FabBaselineTokens.ContainerShape.value
/** Default shape for a small floating action button. */
val smallShape: Shape
- @Composable get() = FabPrimarySmallTokens.ContainerShape.value
+ @Composable get() = FabSmallTokens.ContainerShape.value
/** Default shape for a medium floating action button. */
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
@@ -1000,7 +1021,7 @@
/** Default shape for a large floating action button. */
val largeShape: Shape
- @Composable get() = FabPrimaryLargeTokens.ContainerShape.value
+ @Composable get() = FabLargeTokens.ContainerShape.value
/** Default shape for an extended floating action button. */
val extendedFabShape: Shape
@@ -1011,7 +1032,7 @@
@get:ExperimentalMaterial3ExpressiveApi
@ExperimentalMaterial3ExpressiveApi
val smallExtendedFabShape: Shape
- @Composable get() = ShapeDefaults.Large // TODO: update to use token
+ @Composable get() = ExtendedFabSmallTokens.ContainerShape.value
/** Default shape for a medium extended floating action button. */
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
@@ -1025,11 +1046,11 @@
@get:ExperimentalMaterial3ExpressiveApi
@ExperimentalMaterial3ExpressiveApi
val largeExtendedFabShape: Shape
- @Composable get() = ShapeDefaults.ExtraLarge // TODO: update to use token
+ @Composable get() = ExtendedFabLargeTokens.ContainerShape.value
/** Default container color for a floating action button. */
val containerColor: Color
- @Composable get() = FabPrimaryTokens.ContainerColor.value
+ @Composable get() = FabPrimaryContainerTokens.ContainerColor.value
/**
* Creates a [FloatingActionButtonElevation] that represents the elevation of a
@@ -1044,10 +1065,10 @@
*/
@Composable
fun elevation(
- defaultElevation: Dp = FabPrimaryTokens.ContainerElevation,
- pressedElevation: Dp = FabPrimaryTokens.PressedContainerElevation,
- focusedElevation: Dp = FabPrimaryTokens.FocusContainerElevation,
- hoveredElevation: Dp = FabPrimaryTokens.HoverContainerElevation,
+ defaultElevation: Dp = FabPrimaryContainerTokens.ContainerElevation,
+ pressedElevation: Dp = FabPrimaryContainerTokens.PressedContainerElevation,
+ focusedElevation: Dp = FabPrimaryContainerTokens.FocusedContainerElevation,
+ hoveredElevation: Dp = FabPrimaryContainerTokens.HoveredContainerElevation,
): FloatingActionButtonElevation =
FloatingActionButtonElevation(
defaultElevation = defaultElevation,
@@ -1068,10 +1089,10 @@
*/
@Composable
fun loweredElevation(
- defaultElevation: Dp = FabPrimaryTokens.LoweredContainerElevation,
- pressedElevation: Dp = FabPrimaryTokens.LoweredPressedContainerElevation,
- focusedElevation: Dp = FabPrimaryTokens.LoweredFocusContainerElevation,
- hoveredElevation: Dp = FabPrimaryTokens.LoweredHoverContainerElevation,
+ defaultElevation: Dp = ElevationTokens.Level1,
+ pressedElevation: Dp = ElevationTokens.Level1,
+ focusedElevation: Dp = ElevationTokens.Level1,
+ hoveredElevation: Dp = ElevationTokens.Level2,
): FloatingActionButtonElevation =
FloatingActionButtonElevation(
defaultElevation = defaultElevation,
@@ -1404,22 +1425,27 @@
fun asState(): State<Dp> = animatable.asState()
}
-private val SmallExtendedFabMinimumWidth = 56.dp
-private val SmallExtendedFabMinimumHeight = 56.dp
-private val SmallExtendedFabHorizontalPadding = 16.dp
-private val SmallExtendedFabIconPadding = 8.dp
+private val SmallExtendedFabMinimumWidth = ExtendedFabSmallTokens.ContainerHeight
+private val SmallExtendedFabMinimumHeight = ExtendedFabSmallTokens.ContainerHeight
+private val SmallExtendedFabPaddingStart = ExtendedFabSmallTokens.LeadingSpace
+private val SmallExtendedFabPaddingEnd = ExtendedFabSmallTokens.TrailingSpace
+private val SmallExtendedFabIconPadding = ExtendedFabSmallTokens.IconLabelSpace
private val SmallExtendedFabTextStyle = TypographyKeyTokens.TitleMedium
-private val MediumExtendedFabMinimumWidth = 80.dp
-private val MediumExtendedFabMinimumHeight = 80.dp
-private val MediumExtendedFabHorizontalPadding = 26.dp
-private val MediumExtendedFabIconPadding = 16.dp
+private val MediumExtendedFabMinimumWidth = ExtendedFabMediumTokens.ContainerHeight
+private val MediumExtendedFabMinimumHeight = ExtendedFabMediumTokens.ContainerHeight
+private val MediumExtendedFabPaddingStart = ExtendedFabMediumTokens.LeadingSpace
+private val MediumExtendedFabPaddingEnd = ExtendedFabMediumTokens.TrailingSpace
+// TODO: ExtendedFabMediumTokens.IconLabelSpace is incorrect
+private val MediumExtendedFabIconPadding = 12.dp
private val MediumExtendedFabTextStyle = TypographyKeyTokens.TitleLarge
-private val LargeExtendedFabMinimumWidth = 96.dp
-private val LargeExtendedFabMinimumHeight = 96.dp
-private val LargeExtendedFabHorizontalPadding = 28.dp
-private val LargeExtendedFabIconPadding = 20.dp
+private val LargeExtendedFabMinimumWidth = ExtendedFabLargeTokens.ContainerHeight
+private val LargeExtendedFabMinimumHeight = ExtendedFabLargeTokens.ContainerHeight
+private val LargeExtendedFabPaddingStart = ExtendedFabLargeTokens.LeadingSpace
+private val LargeExtendedFabPaddingEnd = ExtendedFabLargeTokens.TrailingSpace
+// TODO: ExtendedFabLargeTokens.IconLabelSpace is incorrect
+private val LargeExtendedFabIconPadding = 16.dp
private val LargeExtendedFabTextStyle = TypographyKeyTokens.HeadlineSmall
private val ExtendedFabStartIconPadding = 16.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButtonMenu.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButtonMenu.kt
index 1abb428..1fb22a7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButtonMenu.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButtonMenu.kt
@@ -31,9 +31,13 @@
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.toggleable
-import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.GenericShape
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.tokens.FabBaselineTokens
+import androidx.compose.material3.tokens.FabLargeTokens
+import androidx.compose.material3.tokens.FabMediumTokens
+import androidx.compose.material3.tokens.FabMenuBaselineTokens
+import androidx.compose.material3.tokens.FabPrimaryContainerTokens
import androidx.compose.material3.tokens.MotionSchemeKeyTokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -309,7 +313,7 @@
placeable.placeWithLayer(0, 0) { alpha = tempAlphaAnim.value }
}
},
- shape = CircleShape,
+ shape = FabMenuBaselineTokens.ListItemContainerShape.value,
color = containerColor,
contentColor = contentColor,
onClick = onClick
@@ -326,7 +330,10 @@
}
}
.sizeIn(minWidth = FabMenuItemMinWidth, minHeight = FabMenuItemHeight)
- .padding(horizontal = FabMenuItemContentPaddingHorizontal),
+ .padding(
+ start = FabMenuItemContentPaddingStart,
+ end = FabMenuItemContentPaddingEnd
+ ),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement =
Arrangement.spacedBy(
@@ -335,7 +342,10 @@
)
) {
icon()
- text()
+ CompositionLocalProvider(
+ LocalTextStyle provides MaterialTheme.typography.titleMedium,
+ content = text
+ )
}
}
}
@@ -586,26 +596,27 @@
val checkedProgress: Float
}
-private val FabInitialSize = 56.dp
+private val FabInitialSize = FabBaselineTokens.ContainerHeight
private val FabInitialCornerRadius = 16.dp
-private val FabInitialIconSize = 24.dp
-private val FabMediumInitialSize = 80.dp
-private val FabMediumInitialCornerRadius = 24.dp
-private val FabMediumInitialIconSize = 28.dp
-private val FabLargeInitialSize = 96.dp
+private val FabInitialIconSize = FabBaselineTokens.IconSize
+private val FabMediumInitialSize = FabMediumTokens.ContainerHeight
+private val FabMediumInitialCornerRadius = 20.dp
+private val FabMediumInitialIconSize = FabMediumTokens.IconSize
+private val FabLargeInitialSize = FabLargeTokens.ContainerHeight
private val FabLargeInitialCornerRadius = 28.dp
-private val FabLargeInitialIconSize = 36.dp
-private val FabFinalSize = 56.dp
+private val FabLargeInitialIconSize = 36.dp // TODO: FabLargeTokens.IconSize is incorrect
+private val FabFinalSize = FabMenuBaselineTokens.CloseButtonContainerHeight
private val FabFinalCornerRadius = FabFinalSize.div(2)
-private val FabFinalIconSize = 20.dp
-private val FabShadowElevation = 6.dp
+private val FabFinalIconSize = FabMenuBaselineTokens.CloseButtonIconSize
+private val FabShadowElevation = FabPrimaryContainerTokens.ContainerElevation
private val FabMenuPaddingHorizontal = 16.dp
-private val FabMenuPaddingBottom = 8.dp
+private val FabMenuPaddingBottom = FabMenuBaselineTokens.CloseButtonBetweenSpace
private val FabMenuButtonPaddingBottom = 16.dp
-private val FabMenuItemMinWidth = 56.dp
-private val FabMenuItemHeight = 56.dp
-private val FabMenuItemSpacingVertical = 4.dp
-private val FabMenuItemContentPaddingHorizontal = 16.dp
-private val FabMenuItemContentSpacingHorizontal = 8.dp
+private val FabMenuItemMinWidth = FabMenuBaselineTokens.ListItemContainerHeight
+private val FabMenuItemHeight = FabMenuBaselineTokens.ListItemContainerHeight
+private val FabMenuItemSpacingVertical = FabMenuBaselineTokens.ListItemBetweenSpace
+private val FabMenuItemContentPaddingStart = FabMenuBaselineTokens.ListItemLeadingSpace
+private val FabMenuItemContentPaddingEnd = FabMenuBaselineTokens.ListItemTrailingSpace
+private val FabMenuItemContentSpacingHorizontal = FabMenuBaselineTokens.ListItemIconLabelSpace
private const val StaggerEnterDelayMillis = 35
private const val StaggerExitDelayMillis = 25
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
index 3fc2ed9..6337974 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
@@ -30,7 +30,6 @@
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.internal.childSemantics
import androidx.compose.material3.internal.rememberAnimatedShape
-import androidx.compose.material3.tokens.ColorSchemeKeyTokens
import androidx.compose.material3.tokens.FilledIconButtonTokens
import androidx.compose.material3.tokens.FilledTonalIconButtonTokens
import androidx.compose.material3.tokens.LargeIconButtonTokens
@@ -85,14 +84,6 @@
* IconButton with a color tint
*
* @sample androidx.compose.material3.samples.TintedIconButtonSample
- *
- * Small-sized narrow round shape IconButton
- *
- * @sample androidx.compose.material3.samples.XSmallNarrowSquareIconButtonsSample
- *
- * Medium / default size round-shaped icon button
- *
- * @sample androidx.compose.material3.samples.MediumRoundWideIconButtonSample
* @param onClick called when this icon button is clicked
* @param modifier the [Modifier] to be applied to this icon button
* @param enabled controls the enabled state of this icon button. When `false`, this component will
@@ -106,7 +97,6 @@
* interactions will still happen internally.
* @param content the content of this icon button, typically an [Icon]
*/
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Deprecated(
message = "Use overload with `shape`",
replaceWith =
@@ -120,7 +110,7 @@
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
- colors: IconButtonColors = IconButtonDefaults.iconButtonColors(),
+ colors: IconButtonColors = IconButtonDefaults.iconButtonLocalContentColors(),
interactionSource: MutableInteractionSource? = null,
content: @Composable () -> Unit
) {
@@ -156,13 +146,22 @@
* IconButton with a color tint
*
* @sample androidx.compose.material3.samples.TintedIconButtonSample
+ *
+ * Small-sized narrow round shape IconButton
+ *
+ * @sample androidx.compose.material3.samples.XSmallNarrowSquareIconButtonsSample
+ *
+ * Medium / default size round-shaped icon button
+ *
+ * @sample androidx.compose.material3.samples.MediumRoundWideIconButtonSample
* @param onClick called when this icon button is clicked
* @param modifier the [Modifier] to be applied to this icon button
* @param enabled controls the enabled state of this icon button. When `false`, this component will
* not respond to user input, and it will appear visually disabled and disabled to accessibility
* services.
* @param colors [IconButtonColors] that will be used to resolve the colors used for this icon
- * button in different states. See [IconButtonDefaults.iconButtonColors].
+ * button in different states. See [IconButtonDefaults.iconButtonColors] and
+ * [IconButtonDefaults.iconButtonLocalContentColors] .
* @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
* emitting [Interaction]s for this icon button. You can use this to change the icon button's
* appearance or preview the icon button in different states. Note that if `null` is provided,
@@ -176,7 +175,7 @@
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
- colors: IconButtonColors = IconButtonDefaults.iconButtonColors(),
+ colors: IconButtonColors = IconButtonDefaults.iconButtonLocalContentColors(),
interactionSource: MutableInteractionSource? = null,
shape: Shape = IconButtonDefaults.standardShape,
content: @Composable () -> Unit
@@ -250,7 +249,7 @@
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
- colors: IconToggleButtonColors = IconButtonDefaults.iconToggleButtonColors(),
+ colors: IconToggleButtonColors = IconButtonDefaults.iconToggleButtonLocalContentColors(),
interactionSource: MutableInteractionSource? = null,
content: @Composable () -> Unit
) {
@@ -288,7 +287,8 @@
* not respond to user input, and it will appear visually disabled and disabled to accessibility
* services.
* @param colors [IconToggleButtonColors] that will be used to resolve the colors used for this icon
- * button in different states. See [IconButtonDefaults.iconToggleButtonColors].
+ * button in different states. See [IconButtonDefaults.iconToggleButtonColors] and
+ * [IconButtonDefaults.iconToggleButtonLocalContentColors].
* @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
* emitting [Interaction]s for this icon button. You can use this to change the icon button's
* appearance or preview the icon button in different states. Note that if `null` is provided,
@@ -303,7 +303,7 @@
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
- colors: IconToggleButtonColors = IconButtonDefaults.iconToggleButtonColors(),
+ colors: IconToggleButtonColors = IconButtonDefaults.iconToggleButtonLocalContentColors(),
interactionSource: MutableInteractionSource? = null,
shape: Shape = IconButtonDefaults.standardShape,
content: @Composable () -> Unit
@@ -467,62 +467,6 @@
/**
* <a href="https://m3.material.io/components/icon-button/overview" class="external"
- * target="_blank">Material Design filled tonal icon button</a>.
- *
- * Icon buttons help people take supplementary actions with a single tap. They’re used when a
- * compact button is required, such as in a toolbar or image list.
- *
- * 
- *
- * A filled tonal icon button is a medium-emphasis icon button that is an alternative middle ground
- * between the default [FilledIconButton] and [OutlinedIconButton]. They can be used in contexts
- * where the lower-priority icon button requires slightly more emphasis than an outline would give.
- *
- * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
- * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
- * an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
- *
- * Filled tonal icon button sample:
- *
- * @sample androidx.compose.material3.samples.FilledTonalIconButtonSample
- * @param onClick called when this icon button is clicked
- * @param modifier the [Modifier] to be applied to this icon button
- * @param enabled controls the enabled state of this icon button. When `false`, this component will
- * not respond to user input, and it will appear visually disabled and disabled to accessibility
- * services.
- * @param shape defines the shape of this icon button's container
- * @param colors [IconButtonColors] that will be used to resolve the colors used for this icon
- * button in different states. See [IconButtonDefaults.filledIconButtonColors].
- * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
- * emitting [Interaction]s for this icon button. You can use this to change the icon button's
- * appearance or preview the icon button in different states. Note that if `null` is provided,
- * interactions will still happen internally.
- * @param content the content of this icon button, typically an [Icon]
- */
-@Composable
-fun FilledTonalIconButton(
- onClick: () -> Unit,
- modifier: Modifier = Modifier,
- enabled: Boolean = true,
- shape: Shape = IconButtonDefaults.filledShape,
- colors: IconButtonColors = IconButtonDefaults.filledTonalIconButtonColors(),
- interactionSource: MutableInteractionSource? = null,
- content: @Composable () -> Unit
-) =
- SurfaceIconButton(
- onClick = onClick,
- modifier = modifier,
- enabled = enabled,
- shape = shape,
- colors = colors,
- border = null,
- interactionSource = interactionSource,
- content = content
- )
-
-/**
- * <a href="https://m3.material.io/components/icon-button/overview" class="external"
* target="_blank">Material Design filled icon toggle button</a>.
*
* Icon buttons help people take supplementary actions with a single tap. They’re used when a
@@ -635,6 +579,62 @@
/**
* <a href="https://m3.material.io/components/icon-button/overview" class="external"
+ * target="_blank">Material Design filled tonal icon button</a>.
+ *
+ * Icon buttons help people take supplementary actions with a single tap. They’re used when a
+ * compact button is required, such as in a toolbar or image list.
+ *
+ * 
+ *
+ * A filled tonal icon button is a medium-emphasis icon button that is an alternative middle ground
+ * between the default [FilledIconButton] and [OutlinedIconButton]. They can be used in contexts
+ * where the lower-priority icon button requires slightly more emphasis than an outline would give.
+ *
+ * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
+ * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
+ * an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
+ *
+ * Filled tonal icon button sample:
+ *
+ * @sample androidx.compose.material3.samples.FilledTonalIconButtonSample
+ * @param onClick called when this icon button is clicked
+ * @param modifier the [Modifier] to be applied to this icon button
+ * @param enabled controls the enabled state of this icon button. When `false`, this component will
+ * not respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services.
+ * @param shape defines the shape of this icon button's container
+ * @param colors [IconButtonColors] that will be used to resolve the colors used for this icon
+ * button in different states. See [IconButtonDefaults.filledIconButtonColors].
+ * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
+ * emitting [Interaction]s for this icon button. You can use this to change the icon button's
+ * appearance or preview the icon button in different states. Note that if `null` is provided,
+ * interactions will still happen internally.
+ * @param content the content of this icon button, typically an [Icon]
+ */
+@Composable
+fun FilledTonalIconButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ shape: Shape = IconButtonDefaults.filledShape,
+ colors: IconButtonColors = IconButtonDefaults.filledTonalIconButtonColors(),
+ interactionSource: MutableInteractionSource? = null,
+ content: @Composable () -> Unit
+) =
+ SurfaceIconButton(
+ onClick = onClick,
+ modifier = modifier,
+ enabled = enabled,
+ shape = shape,
+ colors = colors,
+ border = null,
+ interactionSource = interactionSource,
+ content = content
+ )
+
+/**
+ * <a href="https://m3.material.io/components/icon-button/overview" class="external"
* target="_blank">Material Design filled tonal icon toggle button</a>.
*
* Icon buttons help people take supplementary actions with a single tap. They’re used when a
@@ -788,9 +788,11 @@
* @param shape defines the shape of this icon button's container and border (when [border] is not
* null)
* @param colors [IconButtonColors] that will be used to resolve the colors used for this icon
- * button in different states. See [IconButtonDefaults.outlinedIconButtonColors].
+ * button in different states. See [IconButtonDefaults.outlinedIconButtonColors] and
+ * [IconButtonDefaults.outlinedIconButtonLocalContentColors].
* @param border the border to draw around the container of this icon button. Pass `null` for no
- * border. See [IconButtonDefaults.outlinedIconButtonBorder].
+ * border. See [IconButtonDefaults.outlinedIconButtonBorder] and
+ * [IconButtonDefaults.outlinedIconButtonLocalContentColorBorder].
* @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
* emitting [Interaction]s for this icon button. You can use this to change the icon button's
* appearance or preview the icon button in different states. Note that if `null` is provided,
@@ -803,8 +805,8 @@
modifier: Modifier = Modifier,
enabled: Boolean = true,
shape: Shape = IconButtonDefaults.outlinedShape,
- colors: IconButtonColors = IconButtonDefaults.outlinedIconButtonColors(),
- border: BorderStroke? = IconButtonDefaults.outlinedIconButtonBorder(enabled),
+ colors: IconButtonColors = IconButtonDefaults.outlinedIconButtonLocalContentColors(),
+ border: BorderStroke? = IconButtonDefaults.outlinedIconButtonLocalContentColorBorder(enabled),
interactionSource: MutableInteractionSource? = null,
content: @Composable () -> Unit
) =
@@ -819,6 +821,125 @@
content = content
)
+/**
+ * <a href="https://m3.material.io/components/icon-button/overview" class="external"
+ * target="_blank">Material Design outlined icon toggle button</a>.
+ *
+ * Icon buttons help people take supplementary actions with a single tap. They’re used when a
+ * compact button is required, such as in a toolbar or image list.
+ *
+ * 
+ *
+ * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
+ * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
+ * an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
+ *
+ * @sample androidx.compose.material3.samples.OutlinedIconToggleButtonSample
+ * @param checked whether this icon button is toggled on or off
+ * @param onCheckedChange called when this icon button is clicked
+ * @param modifier the [Modifier] to be applied to this icon button
+ * @param enabled controls the enabled state of this icon button. When `false`, this component will
+ * not respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services.
+ * @param shape defines the shape of this icon button's container and border (when [border] is not
+ * null)
+ * @param colors [IconToggleButtonColors] that will be used to resolve the colors used for this icon
+ * button in different states. See [IconButtonDefaults.outlinedIconToggleButtonColors] and
+ * [IconButtonDefaults.outlinedIconToggleButtonLocalContentColors].
+ * @param border the border to draw around the container of this icon button. Pass `null` for no
+ * border. See [IconButtonDefaults.outlinedIconToggleButtonBorder] and
+ * [IconButtonDefaults.outlinedIconToggleButtonLocalContentColorBorder].
+ * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
+ * emitting [Interaction]s for this icon button. You can use this to change the icon button's
+ * appearance or preview the icon button in different states. Note that if `null` is provided,
+ * interactions will still happen internally.
+ * @param content the content of this icon button, typically an [Icon]
+ */
+@Composable
+fun OutlinedIconToggleButton(
+ checked: Boolean,
+ onCheckedChange: (Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ shape: Shape = IconButtonDefaults.outlinedShape,
+ colors: IconToggleButtonColors =
+ IconButtonDefaults.outlinedIconToggleButtonLocalContentColors(),
+ border: BorderStroke? =
+ IconButtonDefaults.outlinedIconToggleButtonLocalContentColorBorder(enabled, checked),
+ interactionSource: MutableInteractionSource? = null,
+ content: @Composable () -> Unit
+) =
+ SurfaceIconToggleButton(
+ checked = checked,
+ onCheckedChange = onCheckedChange,
+ modifier = modifier.semantics { role = Role.Checkbox },
+ enabled = enabled,
+ shape = shape,
+ colors = colors,
+ border = border,
+ interactionSource = interactionSource,
+ content = content
+ )
+
+/**
+ * <a href="https://m3.material.io/components/icon-button/overview" class="external"
+ * target="_blank">Material Design outlined icon toggle button</a>.
+ *
+ * Icon buttons help people take supplementary actions with a single tap. They’re used when a
+ * compact button is required, such as in a toolbar or image list.
+ *
+ * 
+ *
+ * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
+ * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
+ * an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
+ *
+ * @sample androidx.compose.material3.samples.OutlinedIconToggleButtonWithAnimatedShapeSample
+ * @param checked whether this icon button is toggled on or off
+ * @param onCheckedChange called when this icon button is clicked
+ * @param shapes the [IconButtonShapes] that the icon toggle button will morph between depending on
+ * the user's interaction with the icon toggle button.
+ * @param modifier the [Modifier] to be applied to this icon button
+ * @param enabled controls the enabled state of this icon button. When `false`, this component will
+ * not respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services.
+ * @param colors [IconToggleButtonColors] that will be used to resolve the colors used for this icon
+ * button in different states. See [IconButtonDefaults.outlinedIconToggleButtonColors].
+ * @param border the border to draw around the container of this icon button. Pass `null` for no
+ * border. See [IconButtonDefaults.outlinedIconToggleButtonBorder].
+ * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
+ * emitting [Interaction]s for this icon button. You can use this to change the icon button's
+ * appearance or preview the icon button in different states. Note that if `null` is provided,
+ * interactions will still happen internally.
+ * @param content the content of this icon button, typically an [Icon]
+ */
+@ExperimentalMaterial3ExpressiveApi
+@Composable
+fun OutlinedIconToggleButton(
+ checked: Boolean,
+ onCheckedChange: (Boolean) -> Unit,
+ shapes: IconButtonShapes,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ colors: IconToggleButtonColors = IconButtonDefaults.outlinedIconToggleButtonColors(),
+ border: BorderStroke? = IconButtonDefaults.outlinedIconToggleButtonBorder(enabled, checked),
+ interactionSource: MutableInteractionSource? = null,
+ content: @Composable () -> Unit
+) =
+ SurfaceIconToggleButton(
+ checked = checked,
+ onCheckedChange = onCheckedChange,
+ modifier = modifier.semantics { role = Role.Checkbox },
+ enabled = enabled,
+ shapes = shapes,
+ colors = colors,
+ border = border,
+ interactionSource = interactionSource,
+ content = content
+ )
+
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun SurfaceIconButton(
@@ -950,132 +1071,20 @@
return shapeByInteraction(shapes, pressed, checked, defaultAnimationSpec)
}
-/**
- * <a href="https://m3.material.io/components/icon-button/overview" class="external"
- * target="_blank">Material Design outlined icon toggle button</a>.
- *
- * Icon buttons help people take supplementary actions with a single tap. They’re used when a
- * compact button is required, such as in a toolbar or image list.
- *
- * 
- *
- * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
- * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
- * an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
- *
- * @sample androidx.compose.material3.samples.OutlinedIconToggleButtonSample
- * @param checked whether this icon button is toggled on or off
- * @param onCheckedChange called when this icon button is clicked
- * @param modifier the [Modifier] to be applied to this icon button
- * @param enabled controls the enabled state of this icon button. When `false`, this component will
- * not respond to user input, and it will appear visually disabled and disabled to accessibility
- * services.
- * @param shape defines the shape of this icon button's container and border (when [border] is not
- * null)
- * @param colors [IconToggleButtonColors] that will be used to resolve the colors used for this icon
- * button in different states. See [IconButtonDefaults.outlinedIconToggleButtonColors].
- * @param border the border to draw around the container of this icon button. Pass `null` for no
- * border. See [IconButtonDefaults.outlinedIconToggleButtonBorder].
- * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
- * emitting [Interaction]s for this icon button. You can use this to change the icon button's
- * appearance or preview the icon button in different states. Note that if `null` is provided,
- * interactions will still happen internally.
- * @param content the content of this icon button, typically an [Icon]
- */
-@Composable
-fun OutlinedIconToggleButton(
- checked: Boolean,
- onCheckedChange: (Boolean) -> Unit,
- modifier: Modifier = Modifier,
- enabled: Boolean = true,
- shape: Shape = IconButtonDefaults.outlinedShape,
- colors: IconToggleButtonColors = IconButtonDefaults.outlinedIconToggleButtonColors(),
- border: BorderStroke? = IconButtonDefaults.outlinedIconToggleButtonBorder(enabled, checked),
- interactionSource: MutableInteractionSource? = null,
- content: @Composable () -> Unit
-) =
- SurfaceIconToggleButton(
- checked = checked,
- onCheckedChange = onCheckedChange,
- modifier = modifier.semantics { role = Role.Checkbox },
- enabled = enabled,
- shape = shape,
- colors = colors,
- border = border,
- interactionSource = interactionSource,
- content = content
- )
-
-/**
- * <a href="https://m3.material.io/components/icon-button/overview" class="external"
- * target="_blank">Material Design outlined icon toggle button</a>.
- *
- * Icon buttons help people take supplementary actions with a single tap. They’re used when a
- * compact button is required, such as in a toolbar or image list.
- *
- * 
- *
- * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
- * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
- * an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
- *
- * @sample androidx.compose.material3.samples.OutlinedIconToggleButtonWithAnimatedShapeSample
- * @param checked whether this icon button is toggled on or off
- * @param onCheckedChange called when this icon button is clicked
- * @param shapes the [IconButtonShapes] that the icon toggle button will morph between depending on
- * the user's interaction with the icon toggle button.
- * @param modifier the [Modifier] to be applied to this icon button
- * @param enabled controls the enabled state of this icon button. When `false`, this component will
- * not respond to user input, and it will appear visually disabled and disabled to accessibility
- * services.
- * @param colors [IconToggleButtonColors] that will be used to resolve the colors used for this icon
- * button in different states. See [IconButtonDefaults.outlinedIconToggleButtonColors].
- * @param border the border to draw around the container of this icon button. Pass `null` for no
- * border. See [IconButtonDefaults.outlinedIconToggleButtonBorder].
- * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
- * emitting [Interaction]s for this icon button. You can use this to change the icon button's
- * appearance or preview the icon button in different states. Note that if `null` is provided,
- * interactions will still happen internally.
- * @param content the content of this icon button, typically an [Icon]
- */
-@ExperimentalMaterial3ExpressiveApi
-@Composable
-fun OutlinedIconToggleButton(
- checked: Boolean,
- onCheckedChange: (Boolean) -> Unit,
- shapes: IconButtonShapes,
- modifier: Modifier = Modifier,
- enabled: Boolean = true,
- colors: IconToggleButtonColors = IconButtonDefaults.outlinedIconToggleButtonColors(),
- border: BorderStroke? = IconButtonDefaults.outlinedIconToggleButtonBorder(enabled, checked),
- interactionSource: MutableInteractionSource? = null,
- content: @Composable () -> Unit
-) =
- SurfaceIconToggleButton(
- checked = checked,
- onCheckedChange = onCheckedChange,
- modifier = modifier.semantics { role = Role.Checkbox },
- enabled = enabled,
- shapes = shapes,
- colors = colors,
- border = border,
- interactionSource = interactionSource,
- content = content
- )
-
-/** Contains the default values used by all icon button types. */
+/** Contains the default values for all four icon and icon toggle button types. */
object IconButtonDefaults {
- /** Creates a [IconButtonColors] that represents the default colors used in a [IconButton]. */
+ /**
+ * Contains the default values used by [IconButton]. [LocalContentColor] will be applied to the
+ * icon and down the UI tree.
+ */
@Composable
- fun iconButtonColors(): IconButtonColors {
+ fun iconButtonLocalContentColors(): IconButtonColors {
val contentColor = LocalContentColor.current
val colors = MaterialTheme.colorScheme.defaultIconButtonColors(contentColor)
- if (colors.contentColor == contentColor) {
- return colors
+ return if (colors.contentColor == contentColor) {
+ colors
} else {
- return colors.copy(
+ colors.copy(
contentColor = contentColor,
disabledContentColor =
contentColor.copy(alpha = StandardIconButtonTokens.DisabledOpacity)
@@ -1084,6 +1093,14 @@
}
/**
+ * Creates a [IconButtonColors] that represents the default colors used in a [IconButton]. See
+ * [iconButtonLocalContentColors] for default values that applies [LocalContentColor] to the
+ * icon and down the UI tree.
+ */
+ @Composable
+ fun iconButtonColors(): IconButtonColors = MaterialTheme.colorScheme.defaultIconButtonColors()
+
+ /**
* Creates a [IconButtonColors] that represents the default colors used in a [IconButton].
*
* @param containerColor the container color of this icon button when enabled.
@@ -1094,13 +1111,13 @@
@Composable
fun iconButtonColors(
containerColor: Color = Color.Unspecified,
- contentColor: Color = LocalContentColor.current,
+ contentColor: Color = Color.Unspecified,
disabledContainerColor: Color = Color.Unspecified,
disabledContentColor: Color =
contentColor.copy(alpha = StandardIconButtonTokens.DisabledOpacity)
): IconButtonColors =
MaterialTheme.colorScheme
- .defaultIconButtonColors(LocalContentColor.current)
+ .defaultIconButtonColors()
.copy(
containerColor = containerColor,
contentColor = contentColor,
@@ -1108,15 +1125,22 @@
disabledContentColor = disabledContentColor,
)
- internal fun ColorScheme.defaultIconButtonColors(localContentColor: Color): IconButtonColors {
+ internal fun ColorScheme.defaultIconButtonColors(
+ localContentColor: Color? = null,
+ ): IconButtonColors {
return defaultIconButtonColorsCached
?: run {
IconButtonColors(
containerColor = Color.Transparent,
- contentColor = localContentColor,
+ contentColor =
+ localContentColor ?: fromToken(StandardIconButtonTokens.Color),
disabledContainerColor = Color.Transparent,
disabledContentColor =
- localContentColor.copy(alpha = StandardIconButtonTokens.DisabledOpacity)
+ localContentColor?.copy(
+ alpha = StandardIconButtonTokens.DisabledOpacity
+ )
+ ?: fromToken(StandardIconButtonTokens.DisabledColor)
+ .copy(alpha = StandardIconButtonTokens.DisabledOpacity)
)
.also { defaultIconButtonColorsCached = it }
}
@@ -1124,10 +1148,10 @@
/**
* Creates a [IconToggleButtonColors] that represents the default colors used in a
- * [IconToggleButton].
+ * [IconToggleButton]. [LocalContentColor] will be applied to the icon and down the UI tree.
*/
@Composable
- fun iconToggleButtonColors(): IconToggleButtonColors {
+ fun iconToggleButtonLocalContentColors(): IconToggleButtonColors {
val contentColor = LocalContentColor.current
val colors = MaterialTheme.colorScheme.defaultIconToggleButtonColors(contentColor)
if (colors.contentColor == contentColor) {
@@ -1143,6 +1167,15 @@
/**
* Creates a [IconToggleButtonColors] that represents the default colors used in a
+ * [IconToggleButton]. See [iconToggleButtonLocalContentColors] for default values that applies
+ * [LocalContentColor] to the icon and down the UI tree.
+ */
+ @Composable
+ fun iconToggleButtonColors(): IconToggleButtonColors =
+ MaterialTheme.colorScheme.defaultIconToggleButtonColors()
+
+ /**
+ * Creates a [IconToggleButtonColors] that represents the default colors used in a
* [IconToggleButton].
*
* @param containerColor the container color of this icon button when enabled.
@@ -1155,7 +1188,7 @@
@Composable
fun iconToggleButtonColors(
containerColor: Color = Color.Unspecified,
- contentColor: Color = LocalContentColor.current,
+ contentColor: Color = Color.Unspecified,
disabledContainerColor: Color = Color.Unspecified,
disabledContentColor: Color =
contentColor.copy(alpha = StandardIconButtonTokens.DisabledOpacity),
@@ -1163,7 +1196,7 @@
checkedContentColor: Color = Color.Unspecified
): IconToggleButtonColors =
MaterialTheme.colorScheme
- .defaultIconToggleButtonColors(LocalContentColor.current)
+ .defaultIconToggleButtonColors()
.copy(
containerColor = containerColor,
contentColor = contentColor,
@@ -1174,18 +1207,22 @@
)
internal fun ColorScheme.defaultIconToggleButtonColors(
- localContentColor: Color
+ localContentColor: Color? = null,
): IconToggleButtonColors {
return defaultIconToggleButtonColorsCached
?: run {
IconToggleButtonColors(
containerColor = Color.Transparent,
- contentColor = localContentColor,
+ contentColor =
+ localContentColor
+ ?: fromToken(StandardIconButtonTokens.UnselectedColor),
disabledContainerColor = Color.Transparent,
disabledContentColor =
- localContentColor.copy(
+ localContentColor?.copy(
alpha = StandardIconButtonTokens.DisabledOpacity
- ),
+ )
+ ?: fromToken(StandardIconButtonTokens.DisabledColor)
+ .copy(alpha = StandardIconButtonTokens.DisabledOpacity),
checkedContainerColor = Color.Transparent,
checkedContentColor = fromToken(StandardIconButtonTokens.SelectedColor)
)
@@ -1227,8 +1264,7 @@
return defaultFilledIconButtonColorsCached
?: IconButtonColors(
containerColor = fromToken(FilledIconButtonTokens.ContainerColor),
- contentColor =
- contentColorFor(fromToken(FilledIconButtonTokens.ContainerColor)),
+ contentColor = fromToken(FilledIconButtonTokens.Color),
disabledContainerColor =
fromToken(FilledIconButtonTokens.DisabledContainerColor)
.copy(alpha = FilledIconButtonTokens.DisabledContainerOpacity),
@@ -1295,10 +1331,7 @@
.copy(alpha = FilledIconButtonTokens.DisabledOpacity),
checkedContainerColor =
fromToken(FilledIconButtonTokens.SelectedContainerColor),
- checkedContentColor =
- contentColorFor(
- fromToken(FilledIconButtonTokens.SelectedContainerColor)
- )
+ checkedContentColor = fromToken(FilledIconButtonTokens.SelectedColor)
)
.also { defaultFilledIconToggleButtonColorsCached = it }
}
@@ -1339,8 +1372,7 @@
return defaultFilledTonalIconButtonColorsCached
?: IconButtonColors(
containerColor = fromToken(FilledTonalIconButtonTokens.ContainerColor),
- contentColor =
- contentColorFor(fromToken(FilledTonalIconButtonTokens.ContainerColor)),
+ contentColor = fromToken(FilledTonalIconButtonTokens.Color),
disabledContainerColor =
fromToken(FilledTonalIconButtonTokens.DisabledContainerColor)
.copy(alpha = FilledTonalIconButtonTokens.DisabledContainerOpacity),
@@ -1394,10 +1426,7 @@
?: IconToggleButtonColors(
containerColor =
fromToken(FilledTonalIconButtonTokens.UnselectedContainerColor),
- contentColor =
- contentColorFor(
- fromToken(FilledTonalIconButtonTokens.UnselectedContainerColor)
- ),
+ contentColor = fromToken(FilledTonalIconButtonTokens.UnselectedColor),
disabledContainerColor =
fromToken(FilledTonalIconButtonTokens.DisabledContainerColor)
.copy(alpha = FilledTonalIconButtonTokens.DisabledContainerOpacity),
@@ -1406,23 +1435,19 @@
.copy(alpha = FilledTonalIconButtonTokens.DisabledOpacity),
checkedContainerColor =
fromToken(FilledTonalIconButtonTokens.SelectedContainerColor),
- checkedContentColor =
- contentColorFor(
- fromToken(FilledTonalIconButtonTokens.SelectedContainerColor)
- )
+ checkedContentColor = fromToken(FilledTonalIconButtonTokens.SelectedColor)
)
.also { defaultFilledTonalIconToggleButtonColorsCached = it }
}
/**
* Creates a [IconButtonColors] that represents the default colors used in a
- * [OutlinedIconButton].
+ * [OutlinedIconButton]. [LocalContentColor] will be applied to the icon and down the UI tree.
*/
@Composable
- fun outlinedIconButtonColors(): IconButtonColors {
- val colors =
- MaterialTheme.colorScheme.defaultOutlinedIconButtonColors(LocalContentColor.current)
+ fun outlinedIconButtonLocalContentColors(): IconButtonColors {
val contentColor = LocalContentColor.current
+ val colors = MaterialTheme.colorScheme.defaultOutlinedIconButtonColors(contentColor)
if (colors.contentColor == contentColor) {
return colors
} else {
@@ -1438,6 +1463,17 @@
* Creates a [IconButtonColors] that represents the default colors used in a
* [OutlinedIconButton].
*
+ * See [outlinedIconButtonLocalContentColors] for default values that applies
+ * [LocalContentColor] to the icon and down the UI tree.
+ */
+ @Composable
+ fun outlinedIconButtonColors(): IconButtonColors =
+ MaterialTheme.colorScheme.defaultOutlinedIconButtonColors()
+
+ /**
+ * Creates a [IconButtonColors] that represents the default colors used in a
+ * [OutlinedIconButton].
+ *
* @param containerColor the container color of this icon button when enabled.
* @param contentColor the content color of this icon button when enabled.
* @param disabledContainerColor the container color of this icon button when not enabled.
@@ -1446,13 +1482,13 @@
@Composable
fun outlinedIconButtonColors(
containerColor: Color = Color.Unspecified,
- contentColor: Color = LocalContentColor.current,
+ contentColor: Color = Color.Unspecified,
disabledContainerColor: Color = Color.Unspecified,
disabledContentColor: Color =
contentColor.copy(alpha = OutlinedIconButtonTokens.DisabledOpacity)
): IconButtonColors =
MaterialTheme.colorScheme
- .defaultOutlinedIconButtonColors(LocalContentColor.current)
+ .defaultOutlinedIconButtonColors()
.copy(
containerColor = containerColor,
contentColor = contentColor,
@@ -1461,16 +1497,21 @@
)
internal fun ColorScheme.defaultOutlinedIconButtonColors(
- localContentColor: Color
+ localContentColor: Color? = null
): IconButtonColors {
return defaultOutlinedIconButtonColorsCached
?: run {
IconButtonColors(
containerColor = Color.Transparent,
- contentColor = localContentColor,
+ contentColor =
+ localContentColor ?: fromToken(OutlinedIconButtonTokens.Color),
disabledContainerColor = Color.Transparent,
disabledContentColor =
- localContentColor.copy(alpha = OutlinedIconButtonTokens.DisabledOpacity)
+ localContentColor?.copy(
+ alpha = OutlinedIconButtonTokens.DisabledOpacity
+ )
+ ?: fromToken(OutlinedIconButtonTokens.DisabledColor)
+ .copy(alpha = OutlinedIconButtonTokens.DisabledOpacity)
)
.also { defaultOutlinedIconButtonColorsCached = it }
}
@@ -1478,10 +1519,11 @@
/**
* Creates a [IconToggleButtonColors] that represents the default colors used in a
- * [OutlinedIconToggleButton].
+ * [OutlinedIconToggleButton]. [LocalContentColor] will be applied to the icon and down the UI
+ * tree.
*/
@Composable
- fun outlinedIconToggleButtonColors(): IconToggleButtonColors {
+ fun outlinedIconToggleButtonLocalContentColors(): IconToggleButtonColors {
val contentColor = LocalContentColor.current
val colors = MaterialTheme.colorScheme.defaultOutlinedIconToggleButtonColors(contentColor)
if (colors.contentColor == contentColor) {
@@ -1499,6 +1541,17 @@
* Creates a [IconToggleButtonColors] that represents the default colors used in a
* [OutlinedIconToggleButton].
*
+ * See [outlinedIconToggleButtonLocalContentColors] for default values that applies
+ * [LocalContentColor] to the icon and down the UI tree.
+ */
+ @Composable
+ fun outlinedIconToggleButtonColors(): IconToggleButtonColors =
+ MaterialTheme.colorScheme.defaultOutlinedIconToggleButtonColors()
+
+ /**
+ * Creates a [IconToggleButtonColors] that represents the default colors used in a
+ * [OutlinedIconToggleButton].
+ *
* @param containerColor the container color of this icon button when enabled.
* @param contentColor the content color of this icon button when enabled.
* @param disabledContainerColor the container color of this icon button when not enabled.
@@ -1509,7 +1562,7 @@
@Composable
fun outlinedIconToggleButtonColors(
containerColor: Color = Color.Unspecified,
- contentColor: Color = LocalContentColor.current,
+ contentColor: Color = Color.Unspecified,
disabledContainerColor: Color = Color.Unspecified,
disabledContentColor: Color =
contentColor.copy(alpha = OutlinedIconButtonTokens.DisabledOpacity),
@@ -1517,7 +1570,7 @@
checkedContentColor: Color = contentColorFor(checkedContainerColor)
): IconToggleButtonColors =
MaterialTheme.colorScheme
- .defaultOutlinedIconToggleButtonColors(LocalContentColor.current)
+ .defaultOutlinedIconToggleButtonColors()
.copy(
containerColor = containerColor,
contentColor = contentColor,
@@ -1528,23 +1581,25 @@
)
internal fun ColorScheme.defaultOutlinedIconToggleButtonColors(
- localContentColor: Color
+ localContentColor: Color? = null
): IconToggleButtonColors {
return defaultOutlinedIconToggleButtonColorsCached
?: run {
IconToggleButtonColors(
containerColor = Color.Transparent,
- contentColor = localContentColor,
+ contentColor =
+ localContentColor
+ ?: fromToken(OutlinedIconButtonTokens.UnselectedColor),
disabledContainerColor = Color.Transparent,
disabledContentColor =
- localContentColor.copy(
+ localContentColor?.copy(
alpha = OutlinedIconButtonTokens.DisabledOpacity
- ),
- checkedContainerColor = fromToken(ColorSchemeKeyTokens.InverseSurface),
- checkedContentColor =
- contentColorFor(
- fromToken(OutlinedIconButtonTokens.SelectedContainerColor)
)
+ ?: fromToken(OutlinedIconButtonTokens.DisabledColor)
+ .copy(alpha = OutlinedIconButtonTokens.DisabledOpacity),
+ checkedContainerColor =
+ fromToken(OutlinedIconButtonTokens.SelectedContainerColor),
+ checkedContentColor = fromToken(OutlinedIconButtonTokens.SelectedColor)
)
.also { defaultOutlinedIconToggleButtonColorsCached = it }
}
@@ -1558,6 +1613,24 @@
* @param checked whether the icon button is checked
*/
@Composable
+ fun outlinedIconToggleButtonLocalContentColorBorder(
+ enabled: Boolean,
+ checked: Boolean
+ ): BorderStroke? {
+ if (checked) {
+ return null
+ }
+ return outlinedIconButtonLocalContentColorBorder(enabled)
+ }
+
+ /**
+ * Represents the [BorderStroke] for an [OutlinedIconButton], depending on its [enabled] and
+ * [checked] state.
+ *
+ * @param enabled whether the icon button is enabled
+ * @param checked whether the icon button is checked
+ */
+ @Composable
fun outlinedIconToggleButtonBorder(enabled: Boolean, checked: Boolean): BorderStroke? {
if (checked) {
return null
@@ -1565,6 +1638,18 @@
return outlinedIconButtonBorder(enabled)
}
+ @Composable
+ fun outlinedIconButtonLocalContentColorBorder(enabled: Boolean): BorderStroke? {
+ val outlineColor = LocalContentColor.current
+ val color: Color =
+ if (enabled) {
+ outlineColor
+ } else {
+ outlineColor.copy(alpha = OutlinedIconButtonTokens.DisabledContainerOpacity)
+ }
+ return remember(color) { BorderStroke(SmallIconButtonTokens.OutlinedOutlineWidth, color) }
+ }
+
/**
* Represents the [BorderStroke] for an [OutlinedIconButton], depending on its [enabled] state.
*
@@ -1572,13 +1657,12 @@
*/
@Composable
fun outlinedIconButtonBorder(enabled: Boolean): BorderStroke {
+ val outlineColor = OutlinedIconButtonTokens.OutlineColor.value
val color: Color =
if (enabled) {
- LocalContentColor.current
+ outlineColor
} else {
- LocalContentColor.current.copy(
- alpha = OutlinedIconButtonTokens.DisabledContainerOpacity
- )
+ outlineColor.copy(alpha = OutlinedIconButtonTokens.DisabledContainerOpacity)
}
return remember(color) { BorderStroke(SmallIconButtonTokens.OutlinedOutlineWidth, color) }
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialShapes.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialShapes.kt
index 9b94f2b..b0ac90c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialShapes.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialShapes.kt
@@ -34,6 +34,7 @@
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMaxBy
import androidx.graphics.shapes.CornerRounding
+import androidx.graphics.shapes.Morph
import androidx.graphics.shapes.RoundedPolygon
import androidx.graphics.shapes.TransformResult
import androidx.graphics.shapes.circle
@@ -46,11 +47,23 @@
import kotlin.math.sin
/**
- * Returns a normalized [Path] that is remembered across compositions for this [RoundedPolygon].
+ * Returns a [Path] for this [Morph].
+ *
+ * @param progress the [Morph]'s progress
+ * @param path a [Path] to rewind and set with the new path data. In case provided, this Path would
+ * be the returned one.
+ * @param startAngle an angle to rotate the [Path] to start drawing from
+ */
+@ExperimentalMaterial3ExpressiveApi
+fun Morph.toPath(progress: Float, path: Path = Path(), startAngle: Int = 0): Path {
+ return this.toPath(path = path, progress = progress, startAngle = startAngle)
+}
+
+/**
+ * Returns a [Path] that is remembered across compositions for this [RoundedPolygon].
*
* @param startAngle an angle to rotate the Material shape's path to start drawing from. The
* rotation pivot is set to be the shape's centerX and centerY coordinates.
- * @see RoundedPolygon.normalized
*/
@ExperimentalMaterial3ExpressiveApi
@Composable
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MotionScheme.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MotionScheme.kt
index c39448b..b19a579 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MotionScheme.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MotionScheme.kt
@@ -18,10 +18,11 @@
import androidx.compose.animation.core.AnimationVector
import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.TwoWayConverter
import androidx.compose.animation.core.spring
+import androidx.compose.material3.tokens.ExpressiveMotionTokens
import androidx.compose.material3.tokens.MotionSchemeKeyTokens
+import androidx.compose.material3.tokens.StandardMotionTokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
@@ -225,27 +226,45 @@
fun standardMotionScheme(): MotionScheme =
object : MotionScheme {
override fun <T> defaultSpatialSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = StandardSpatialDampingRatio, stiffness = 700f)
+ return spring(
+ dampingRatio = StandardMotionTokens.SpringDefaultSpatialDamping,
+ stiffness = StandardMotionTokens.SpringDefaultSpatialStiffness
+ )
}
override fun <T> fastSpatialSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = StandardSpatialDampingRatio, stiffness = 1400f)
+ return spring(
+ dampingRatio = StandardMotionTokens.SpringFastSpatialDamping,
+ stiffness = StandardMotionTokens.SpringFastSpatialStiffness
+ )
}
override fun <T> slowSpatialSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = StandardSpatialDampingRatio, stiffness = 300f)
+ return spring(
+ dampingRatio = StandardMotionTokens.SpringSlowSpatialDamping,
+ stiffness = StandardMotionTokens.SpringSlowSpatialStiffness
+ )
}
override fun <T> defaultEffectsSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = EffectsDampingRatio, stiffness = EffectsDefaultStiffness)
+ return spring(
+ dampingRatio = StandardMotionTokens.SpringDefaultEffectsDamping,
+ stiffness = StandardMotionTokens.SpringDefaultEffectsStiffness
+ )
}
override fun <T> fastEffectsSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = EffectsDampingRatio, stiffness = EffectsFastStiffness)
+ return spring(
+ dampingRatio = StandardMotionTokens.SpringFastEffectsDamping,
+ stiffness = StandardMotionTokens.SpringFastEffectsStiffness
+ )
}
override fun <T> slowEffectsSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = EffectsDampingRatio, stiffness = EffectsSlowStiffness)
+ return spring(
+ dampingRatio = StandardMotionTokens.SpringSlowEffectsDamping,
+ stiffness = StandardMotionTokens.SpringSlowEffectsStiffness
+ )
}
}
@@ -254,27 +273,45 @@
fun expressiveMotionScheme(): MotionScheme =
object : MotionScheme {
override fun <T> defaultSpatialSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = 0.8f, stiffness = 380f)
+ return spring(
+ dampingRatio = ExpressiveMotionTokens.SpringDefaultSpatialDamping,
+ stiffness = ExpressiveMotionTokens.SpringDefaultSpatialStiffness
+ )
}
override fun <T> fastSpatialSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = 0.6f, stiffness = 800f)
+ return spring(
+ dampingRatio = ExpressiveMotionTokens.SpringFastSpatialDamping,
+ stiffness = ExpressiveMotionTokens.SpringFastSpatialStiffness
+ )
}
override fun <T> slowSpatialSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = 0.8f, stiffness = 200f)
+ return spring(
+ dampingRatio = ExpressiveMotionTokens.SpringSlowSpatialDamping,
+ stiffness = ExpressiveMotionTokens.SpringSlowSpatialStiffness
+ )
}
override fun <T> defaultEffectsSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = EffectsDampingRatio, stiffness = EffectsDefaultStiffness)
+ return spring(
+ dampingRatio = ExpressiveMotionTokens.SpringDefaultEffectsDamping,
+ stiffness = ExpressiveMotionTokens.SpringDefaultEffectsStiffness
+ )
}
override fun <T> fastEffectsSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = EffectsDampingRatio, stiffness = EffectsFastStiffness)
+ return spring(
+ dampingRatio = ExpressiveMotionTokens.SpringFastEffectsDamping,
+ stiffness = ExpressiveMotionTokens.SpringFastEffectsStiffness
+ )
}
override fun <T> slowEffectsSpec(): FiniteAnimationSpec<T> {
- return spring(dampingRatio = EffectsDampingRatio, stiffness = EffectsSlowStiffness)
+ return spring(
+ dampingRatio = ExpressiveMotionTokens.SpringSlowEffectsDamping,
+ stiffness = ExpressiveMotionTokens.SpringSlowEffectsStiffness
+ )
}
}
@@ -323,12 +360,3 @@
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
internal inline fun <reified T> MotionSchemeKeyTokens.value(): FiniteAnimationSpec<T> =
MaterialTheme.motionScheme.fromToken(this)
-
-// Common effects damping and stiffness values for both Standard and Expressive
-private const val EffectsDampingRatio = Spring.DampingRatioNoBouncy
-private const val EffectsDefaultStiffness = 1600f
-private const val EffectsFastStiffness = 3800f
-private const val EffectsSlowStiffness = 800f
-
-// Common damping for Standard spatial specs
-private const val StandardSpatialDampingRatio = 0.9f
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
index 024072c..4cf6819 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
@@ -16,8 +16,6 @@
package androidx.compose.material3
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.Interaction
@@ -30,28 +28,23 @@
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.internal.ProvideContentColorTextStyle
+import androidx.compose.material3.internal.rememberAnimatedShape
+import androidx.compose.material3.tokens.BaselineButtonTokens
import androidx.compose.material3.tokens.MotionSchemeKeyTokens
-import androidx.compose.material3.tokens.ShapeTokens
import androidx.compose.material3.tokens.SplitButtonSmallTokens
import androidx.compose.material3.tokens.StateTokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.Stable
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.Modifier
import androidx.compose.ui.draw.drawWithContent
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.layout.Layout
@@ -60,9 +53,7 @@
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.constrainHeight
import androidx.compose.ui.unit.constrainWidth
import androidx.compose.ui.unit.dp
@@ -70,9 +61,6 @@
import androidx.compose.ui.util.fastFirst
import androidx.compose.ui.util.fastMaxOfOrNull
import androidx.compose.ui.util.fastSumBy
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
/**
* A [SplitButton] let user define a button group consisting of 2 buttons. The leading button
@@ -474,34 +462,41 @@
)
}
-// TODO Replace default value with tokens
/** Contains default values used by [SplitButton] and its style variants. */
@ExperimentalMaterial3ExpressiveApi
object SplitButtonDefaults {
/** Default icon size for the leading button */
- val LeadingIconSize = 20.dp
+ val LeadingIconSize = BaselineButtonTokens.IconSize
/** Default icon size for the trailing button */
- val TrailingIconSize = 22.dp
+ val TrailingIconSize = SplitButtonSmallTokens.TrailingIconSize
/** Default spacing between the `leading` and `trailing` button */
val Spacing = SplitButtonSmallTokens.BetweenSpace
/** Default size for the leading button end corners and trailing button start corners */
- val InnerCornerSize = CornerSize(4.dp)
-
- private val InnerCornerSizePressed = CornerSize(8.dp)
+ // TODO update token to dp size and use it here
+ val InnerCornerSize = SplitButtonSmallTokens.InnerCornerSize
+ private val InnerCornerSizePressed = ShapeDefaults.CornerSmall
/**
* Default percentage size for the leading button start corners and trailing button end corners
*/
- val OuterCornerSize = CornerSize(50)
+ val OuterCornerSize = ShapeDefaults.CornerFull
/** Default content padding of the leading button */
- val LeadingButtonContentPadding = PaddingValues(16.dp, 10.dp, 12.dp, 10.dp)
+ val LeadingButtonContentPadding =
+ PaddingValues(
+ start = SplitButtonSmallTokens.LeadingButtonLeadingSpace,
+ end = SplitButtonSmallTokens.LeadingButtonTrailingSpace
+ )
/** Default content padding of the trailing button */
- val TrailingButtonContentPadding = PaddingValues(horizontal = 13.dp)
+ val TrailingButtonContentPadding =
+ PaddingValues(
+ start = SplitButtonSmallTokens.TrailingButtonLeadingSpace,
+ end = SplitButtonSmallTokens.TrailingButtonTrailingSpace
+ )
/**
* Default minimum width of the [LeadingButton], applies to all 4 variants of the split button
@@ -517,7 +512,7 @@
/** Default minimum width of the [TrailingButton]. */
private val TrailingButtonMinWidth = LeadingButtonMinWidth
- /** Trailng button state layer alpha when in checked state */
+ /** Trailing button state layer alpha when in checked state */
private const val TrailingButtonStateLayerAlpha = StateTokens.PressedStateLayerOpacity
/** Default shape of the leading button. */
@@ -538,7 +533,7 @@
topEnd = OuterCornerSize,
bottomEnd = OuterCornerSize
)
- private val TrailingCheckedShape = ShapeTokens.CornerFull
+ private val TrailingCheckedShape = CircleShape
/**
* Default shapes for the leading button. This defines the shapes the leading button should
@@ -579,9 +574,10 @@
* @param onClick called when the button is clicked
* @param modifier the [Modifier] to be applied to this button.
* @param enabled controls the enabled state of the split button. When `false`, this component
- * will
- * @param shapes defines the shapes of this button's container, border (when [border] is not
- * null), and shadow (when using [elevation])
+ * will not respond to user input, and it will appear visually disabled and disabled to
+ * accessibility services.
+ * @param shapes the [SplitButtonShapes] that the trailing button will morph between depending
+ * on the user's interaction with the button.
* @param colors [ButtonColors] that will be used to resolve the colors for this button in
* different states. See [ButtonDefaults.buttonColors].
* @param elevation [ButtonElevation] used to resolve the elevation for this button in different
@@ -659,9 +655,10 @@
* trigger the corner morphing animation to reflect the updated state.
* @param modifier the [Modifier] to be applied to this button.
* @param enabled controls the enabled state of the split button. When `false`, this component
- * will
- * @param shapes the shapes for the container when transition between different
- * [ShapeAnimationState]
+ * will not respond to user input, and it will appear visually disabled and disabled to
+ * accessibility services.
+ * @param shapes the [SplitButtonShapes] that the trailing button will morph between depending
+ * on the user's interaction with the button.
* @param colors [ButtonColors] that will be used to resolve the colors for this button in
* different states. See [ButtonDefaults.buttonColors].
* @param elevation [ButtonElevation] used to resolve the elevation for this button in different
@@ -901,92 +898,17 @@
checked: Boolean,
animationSpec: FiniteAnimationSpec<Float>
): Shape {
+ val shape =
+ if (pressed) {
+ shapes.pressedShape ?: shapes.shape
+ } else if (checked) {
+ shapes.checkedShape ?: shapes.shape
+ } else shapes.shape
+
if (shapes.hasRoundedCornerShapes) {
- return rememberAnimatedShape(shapes, pressed, checked, animationSpec)
+ return rememberAnimatedShape(shape as RoundedCornerShape, animationSpec)
}
- if (pressed) {
- return shapes.pressedShape ?: shapes.shape
- }
- if (checked) {
- return shapes.checkedShape ?: shapes.shape
- }
- return shapes.shape
-}
-
-@Composable
-private fun rememberAnimatedShape(
- shapes: SplitButtonShapes,
- pressed: Boolean,
- checked: Boolean,
- animationSpec: FiniteAnimationSpec<Float>,
-): Shape {
- val defaultShape = shapes.shape as RoundedCornerShape
- val pressedShape = shapes.pressedShape as RoundedCornerShape?
- val checkedShape = shapes.checkedShape as RoundedCornerShape?
- val currentShape =
- if (pressedShape != null && pressed) pressedShape
- else if (checkedShape != null && checked) checkedShape else defaultShape
-
- val state =
- remember(animationSpec) {
- ShapeAnimationState(
- shape = currentShape,
- spec = animationSpec,
- )
- }
-
- val channel = remember { Channel<RoundedCornerShape>(Channel.CONFLATED) }
- SideEffect { channel.trySend(currentShape) }
- LaunchedEffect(state, channel) {
- for (target in channel) {
- val newTarget = channel.tryReceive().getOrNull() ?: target
- launch { state.animateToShape(newTarget) }
- }
- }
-
- return rememberAnimatedShape(state)
-}
-
-@Composable
-private fun rememberAnimatedShape(state: ShapeAnimationState): Shape {
- val density = LocalDensity.current
- state.density = density
-
- return remember(density, state) {
- object : ShapeWithOpticalCentering {
- var clampedRange by mutableStateOf(0f..1f)
-
- override fun offset(): Float {
- val topStart = state.topStart?.value?.coerceIn(clampedRange) ?: 0f
- val topEnd = state.topEnd?.value?.coerceIn(clampedRange) ?: 0f
- val bottomStart = state.bottomStart?.value?.coerceIn(clampedRange) ?: 0f
- val bottomEnd = state.bottomEnd?.value?.coerceIn(clampedRange) ?: 0f
- val avgStart = (topStart + bottomStart) / 2
- val avgEnd = (topEnd + bottomEnd) / 2
- return OpticalCenteringCoefficient * (avgStart - avgEnd)
- }
-
- override fun createOutline(
- size: Size,
- layoutDirection: LayoutDirection,
- density: Density
- ): Outline {
- state.size = size
- if (!state.didInit) {
- state.init()
- }
-
- clampedRange = 0f..size.height / 2
- return RoundedCornerShape(
- topStart = state.topStart?.value?.coerceIn(clampedRange) ?: 0f,
- topEnd = state.topEnd?.value?.coerceIn(clampedRange) ?: 0f,
- bottomStart = state.bottomStart?.value?.coerceIn(clampedRange) ?: 0f,
- bottomEnd = state.bottomEnd?.value?.coerceIn(clampedRange) ?: 0f,
- )
- .createOutline(size, layoutDirection, density)
- }
- }
- }
+ return shape
}
/**
@@ -1007,45 +929,5 @@
return shape is RoundedCornerShape
}
-@Stable
-private class ShapeAnimationState(
- val shape: RoundedCornerShape,
- val spec: FiniteAnimationSpec<Float>,
-) {
- var size: Size = Size.Zero
- var density: Density = Density(0f, 0f)
- var didInit = false
-
- var topStart: Animatable<Float, AnimationVector1D>? = null
- private set
-
- var topEnd: Animatable<Float, AnimationVector1D>? = null
- private set
-
- var bottomStart: Animatable<Float, AnimationVector1D>? = null
- private set
-
- var bottomEnd: Animatable<Float, AnimationVector1D>? = null
- private set
-
- fun init() {
- topStart = Animatable(shape.topStart.toPx(size, density))
- topEnd = Animatable(shape.topEnd.toPx(size, density))
- bottomStart = Animatable(shape.bottomStart.toPx(size, density))
- bottomEnd = Animatable(shape.bottomEnd.toPx(size, density))
- didInit = true
- }
-
- suspend fun animateToShape(shape: RoundedCornerShape?) =
- shape?.let {
- coroutineScope {
- launch { topStart?.animateTo(it.topStart.toPx(size, density), spec) }
- launch { topEnd?.animateTo(it.topEnd.toPx(size, density), spec) }
- launch { bottomStart?.animateTo(it.bottomStart.toPx(size, density), spec) }
- launch { bottomEnd?.animateTo(it.bottomEnd.toPx(size, density), spec) }
- }
- }
-}
-
private const val LeadingButtonLayoutId = "LeadingButton"
private const val TrailingButtonLayoutId = "TrailingButton"
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/AnchoredDraggable.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/AnchoredDraggable.kt
index fa71348..09b667b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/AnchoredDraggable.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/AnchoredDraggable.kt
@@ -565,7 +565,7 @@
if (anchors.hasAnchorFor(targetValue)) {
try {
dragMutex.mutate(dragPriority) {
- dragTarget = targetValue
+ dragTarget = if (confirmValueChange(targetValue)) targetValue else currentValue
restartable(inputs = { anchors to [email protected] }) {
(latestAnchors, latestTarget) ->
anchoredDragScope.block(latestAnchors, latestTarget)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt
index 73196bb..639db61 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt
@@ -319,12 +319,11 @@
if (isRefreshing) return 0f // Already refreshing, do nothing
// Trigger refresh
if (adjustedDistancePulled > thresholdPx) {
- animateToThreshold()
onRefresh()
- } else {
- animateToHidden()
}
+ animateToHidden()
+
val consumed =
when {
// We are flinging without having dragged the pull refresh (for example a fling
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExpressiveMotionTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExpressiveMotionTokens.kt
new file mode 100644
index 0000000..3362be1
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExpressiveMotionTokens.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 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.
+ */
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+internal object ExpressiveMotionTokens {
+ val SpringDefaultSpatialDamping = 0.8f
+ val SpringDefaultSpatialStiffness = 380.0f
+ val SpringDefaultEffectsDamping = 1.0f
+ val SpringDefaultEffectsStiffness = 1600.0f
+ val SpringFastSpatialDamping = 0.6f
+ val SpringFastSpatialStiffness = 800.0f
+ val SpringFastEffectsDamping = 1.0f
+ val SpringFastEffectsStiffness = 3800.0f
+ val SpringSlowSpatialDamping = 0.8f
+ val SpringSlowSpatialStiffness = 200.0f
+ val SpringSlowEffectsDamping = 1.0f
+ val SpringSlowEffectsStiffness = 800.0f
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExtendedFabLargeTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExtendedFabLargeTokens.kt
new file mode 100644
index 0000000..7b339a7
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExtendedFabLargeTokens.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 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.
+ */
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object ExtendedFabLargeTokens {
+ val ContainerHeight = 96.0.dp
+ val ContainerShape = ShapeKeyTokens.CornerExtraLarge
+ val IconLabelSpace = 20.0.dp
+ val IconSize = 32.0.dp
+ val LeadingSpace = 28.0.dp
+ val TrailingSpace = 28.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExtendedFabMediumTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExtendedFabMediumTokens.kt
new file mode 100644
index 0000000..3f6c72ff
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExtendedFabMediumTokens.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 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.
+ */
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object ExtendedFabMediumTokens {
+ val ContainerHeight = 80.0.dp
+ // TODO: uncomment when ShapeKeyTokens.CornerLargeIncreased is available
+ // val ContainerShape = ShapeKeyTokens.CornerLargeIncreased
+ val IconLabelSpace = 16.0.dp
+ val IconSize = 28.0.dp
+ val LeadingSpace = 26.0.dp
+ val TrailingSpace = 26.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExtendedFabSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExtendedFabSmallTokens.kt
new file mode 100644
index 0000000..6a51bb1
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ExtendedFabSmallTokens.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 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.
+ */
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object ExtendedFabSmallTokens {
+ val ContainerHeight = 56.0.dp
+ val ContainerShape = ShapeKeyTokens.CornerLarge
+ val IconLabelSpace = 8.0.dp
+ val IconSize = 24.0.dp
+ val LeadingSpace = 16.0.dp
+ val TrailingSpace = 16.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabBaselineTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabBaselineTokens.kt
new file mode 100644
index 0000000..f4f3506
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabBaselineTokens.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2024 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.
+ */
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object FabBaselineTokens {
+ val ContainerHeight = 56.0.dp
+ val ContainerShape = ShapeKeyTokens.CornerLarge
+ val ContainerWidth = 56.0.dp
+ val IconSize = 24.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabLargeTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabLargeTokens.kt
new file mode 100644
index 0000000..57421b1
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabLargeTokens.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2024 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.
+ */
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object FabLargeTokens {
+ val ContainerHeight = 96.0.dp
+ val ContainerShape = ShapeKeyTokens.CornerExtraLarge
+ val ContainerWidth = 96.0.dp
+ val IconSize = 32.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabMediumTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabMediumTokens.kt
new file mode 100644
index 0000000..70853b3
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabMediumTokens.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024 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.
+ */
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object FabMediumTokens {
+ val ContainerHeight = 80.0.dp
+ // TODO: uncomment when ShapeKeyTokens.CornerLargeIncreased is available
+ // val ContainerShape = ShapeKeyTokens.CornerLargeIncreased
+ val ContainerWidth = 80.0.dp
+ val IconSize = 28.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabMenuBaselineTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabMenuBaselineTokens.kt
new file mode 100644
index 0000000..4c8771a
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabMenuBaselineTokens.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 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.
+ */
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object FabMenuBaselineTokens {
+ val CloseButtonBetweenSpace = 8.0.dp
+ val CloseButtonContainerElevation = ElevationTokens.Level3
+ val CloseButtonContainerHeight = 56.0.dp
+ val CloseButtonContainerShape = ShapeKeyTokens.CornerFull
+ val CloseButtonContainerWidth = 56.0.dp
+ val CloseButtonIconSize = 20.0.dp
+ val ListItemBetweenSpace = 4.0.dp
+ val ListItemContainerElevation = ElevationTokens.Level3
+ val ListItemContainerHeight = 56.0.dp
+ val ListItemContainerShape = ShapeKeyTokens.CornerFull
+ val ListItemIconLabelSpace = 8.0.dp
+ val ListItemIconSize = 24.0.dp
+ val ListItemLeadingSpace = 24.0.dp
+ val ListItemTrailingSpace = 24.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimaryContainerTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimaryContainerTokens.kt
new file mode 100644
index 0000000..4f273ca
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimaryContainerTokens.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 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.
+ */
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+internal object FabPrimaryContainerTokens {
+ val ContainerColor = ColorSchemeKeyTokens.PrimaryContainer
+ val ContainerElevation = ElevationTokens.Level3
+ val FocusedContainerElevation = ElevationTokens.Level3
+ val FocusedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val HoveredContainerElevation = ElevationTokens.Level4
+ val HoveredIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val IconColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val PressedContainerElevation = ElevationTokens.Level3
+ val PressedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimaryLargeTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimaryLargeTokens.kt
deleted file mode 100644
index 2330a5c..0000000
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimaryLargeTokens.kt
+++ /dev/null
@@ -1,41 +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.
- */
-// VERSION: v0_103
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-package androidx.compose.material3.tokens
-
-import androidx.compose.ui.unit.dp
-
-internal object FabPrimaryLargeTokens {
- val ContainerColor = ColorSchemeKeyTokens.PrimaryContainer
- val ContainerElevation = ElevationTokens.Level3
- val ContainerHeight = 96.0.dp
- val ContainerShape = ShapeKeyTokens.CornerExtraLarge
- val ContainerWidth = 96.0.dp
- val FocusContainerElevation = ElevationTokens.Level3
- val FocusIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
- val HoverContainerElevation = ElevationTokens.Level4
- val HoverIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
- val IconColor = ColorSchemeKeyTokens.OnPrimaryContainer
- val IconSize = 36.0.dp
- val LoweredContainerElevation = ElevationTokens.Level1
- val LoweredFocusContainerElevation = ElevationTokens.Level1
- val LoweredHoverContainerElevation = ElevationTokens.Level2
- val LoweredPressedContainerElevation = ElevationTokens.Level1
- val PressedContainerElevation = ElevationTokens.Level3
- val PressedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimarySmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimarySmallTokens.kt
deleted file mode 100644
index 106084a..0000000
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimarySmallTokens.kt
+++ /dev/null
@@ -1,41 +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.
- */
-// VERSION: v0_103
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-package androidx.compose.material3.tokens
-
-import androidx.compose.ui.unit.dp
-
-internal object FabPrimarySmallTokens {
- val ContainerColor = ColorSchemeKeyTokens.PrimaryContainer
- val ContainerElevation = ElevationTokens.Level3
- val ContainerHeight = 40.0.dp
- val ContainerShape = ShapeKeyTokens.CornerMedium
- val ContainerWidth = 40.0.dp
- val FocusContainerElevation = ElevationTokens.Level3
- val FocusIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
- val HoverContainerElevation = ElevationTokens.Level4
- val HoverIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
- val IconColor = ColorSchemeKeyTokens.OnPrimaryContainer
- val IconSize = 24.0.dp
- val LoweredContainerElevation = ElevationTokens.Level1
- val LoweredFocusContainerElevation = ElevationTokens.Level1
- val LoweredHoverContainerElevation = ElevationTokens.Level2
- val LoweredPressedContainerElevation = ElevationTokens.Level1
- val PressedContainerElevation = ElevationTokens.Level3
- val PressedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimaryTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimaryTokens.kt
deleted file mode 100644
index 6ee7c71..0000000
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabPrimaryTokens.kt
+++ /dev/null
@@ -1,41 +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.
- */
-// VERSION: v0_103
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-package androidx.compose.material3.tokens
-
-import androidx.compose.ui.unit.dp
-
-internal object FabPrimaryTokens {
- val ContainerColor = ColorSchemeKeyTokens.PrimaryContainer
- val ContainerElevation = ElevationTokens.Level3
- val ContainerHeight = 56.0.dp
- val ContainerShape = ShapeKeyTokens.CornerLarge
- val ContainerWidth = 56.0.dp
- val FocusContainerElevation = ElevationTokens.Level3
- val FocusIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
- val HoverContainerElevation = ElevationTokens.Level4
- val HoverIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
- val IconColor = ColorSchemeKeyTokens.OnPrimaryContainer
- val IconSize = 24.0.dp
- val LoweredContainerElevation = ElevationTokens.Level1
- val LoweredFocusContainerElevation = ElevationTokens.Level1
- val LoweredHoverContainerElevation = ElevationTokens.Level2
- val LoweredPressedContainerElevation = ElevationTokens.Level1
- val PressedContainerElevation = ElevationTokens.Level3
- val PressedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabSecondaryContainerTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabSecondaryContainerTokens.kt
new file mode 100644
index 0000000..a419646
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabSecondaryContainerTokens.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 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.
+ */
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+internal object FabSecondaryContainerTokens {
+ val ContainerColor = ColorSchemeKeyTokens.SecondaryContainer
+ val ContainerElevation = ElevationTokens.Level3
+ val FocusedContainerElevation = ElevationTokens.Level3
+ val FocusedIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
+ val HoveredContainerElevation = ElevationTokens.Level4
+ val HoveredIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
+ val IconColor = ColorSchemeKeyTokens.OnSecondaryContainer
+ val PressedContainerElevation = ElevationTokens.Level3
+ val PressedIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabSecondaryTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabSecondaryTokens.kt
deleted file mode 100644
index ce17e1b..0000000
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabSecondaryTokens.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.
- */
-// VERSION: v0_103
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-package androidx.compose.material3.tokens
-
-import androidx.compose.ui.unit.dp
-
-internal object FabSecondaryTokens {
- val ContainerColor = ColorSchemeKeyTokens.SecondaryContainer
- val ContainerElevation = ElevationTokens.Level3
- val ContainerHeight = 56.0.dp
- val ContainerShape = ShapeKeyTokens.CornerLarge
- val ContainerWidth = 56.0.dp
- val FocusContainerElevation = ElevationTokens.Level3
- val FocusIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
- val HoverContainerElevation = ElevationTokens.Level4
- val HoverIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
- val IconColor = ColorSchemeKeyTokens.OnSecondaryContainer
- val IconSize = 24.0.dp
- val LoweredContainerElevation = ElevationTokens.Level1
- val LoweredFocusContainerElevation = ElevationTokens.Level1
- val LoweredHoverContainerElevation = ElevationTokens.Level2
- val LoweredPressedContainerElevation = ElevationTokens.Level1
- val PressedContainerElevation = ElevationTokens.Level3
- val PressedIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabSmallTokens.kt
similarity index 60%
copy from tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt
copy to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabSmallTokens.kt
index 3fa6028..da6ea8e 100644
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FabSmallTokens.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,10 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
-package androidx.tv.integration.presentation
+package androidx.compose.material3.tokens
-import android.content.res.AssetManager
+import androidx.compose.ui.unit.dp
-fun AssetManager.readAssetsFile(fileName: String): String =
- open(fileName).bufferedReader().use { it.readText() }
+internal object FabSmallTokens {
+ val ContainerHeight = 40.0.dp
+ val ContainerShape = ShapeKeyTokens.CornerMedium
+ val ContainerWidth = 40.0.dp
+ val IconSize = 24.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarHorizontalItemTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarHorizontalItemTokens.kt
index bdecc5f..fba7659 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarHorizontalItemTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarHorizontalItemTokens.kt
@@ -13,13 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// VERSION: v0_11_0
// GENERATED CODE - DO NOT MODIFY BY HAND
-// TO MODIFY THIS FILE UPDATE:
-// ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
-//
-// Design System: sandbox
-// Version: v0_9_0
-// Audience: 1p
package androidx.compose.material3.tokens
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt
index 4842560..352fe47 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt
@@ -13,13 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// VERSION: v0_11_0
// GENERATED CODE - DO NOT MODIFY BY HAND
-// TO MODIFY THIS FILE UPDATE:
-// ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
-//
-// Design System: sandbox
-// Version: v0_9_0
-// Audience: 1p
package androidx.compose.material3.tokens
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarVerticalItemTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarVerticalItemTokens.kt
index 5452b98..c901af7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarVerticalItemTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarVerticalItemTokens.kt
@@ -13,13 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// VERSION: v0_11_0
// GENERATED CODE - DO NOT MODIFY BY HAND
-// TO MODIFY THIS FILE UPDATE:
-// ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
-//
-// Design System: sandbox
-// Version: v0_9_0
-// Audience: 1p
package androidx.compose.material3.tokens
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailBaselineItemTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailBaselineItemTokens.kt
index 144c675..7e5c0b3 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailBaselineItemTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailBaselineItemTokens.kt
@@ -13,13 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// VERSION: v0_11_0
// GENERATED CODE - DO NOT MODIFY BY HAND
-// TO MODIFY THIS FILE UPDATE:
-// ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
-//
-// Design System: sandbox
-// Version: v0_9_0
-// Audience: 1p
package androidx.compose.material3.tokens
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailCollapsedTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailCollapsedTokens.kt
index 4827019..c4ad2b0 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailCollapsedTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailCollapsedTokens.kt
@@ -13,13 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// VERSION: v0_11_0
// GENERATED CODE - DO NOT MODIFY BY HAND
-// TO MODIFY THIS FILE UPDATE:
-// ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
-//
-// Design System: sandbox
-// Version: v0_9_0
-// Audience: 1p
package androidx.compose.material3.tokens
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailColorTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailColorTokens.kt
index 951345a..68a5be1 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailColorTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailColorTokens.kt
@@ -13,13 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// VERSION: v0_11_0
// GENERATED CODE - DO NOT MODIFY BY HAND
-// TO MODIFY THIS FILE UPDATE:
-// ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
-//
-// Design System: sandbox
-// Version: v0_9_0
-// Audience: 1p
package androidx.compose.material3.tokens
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailExpandedTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailExpandedTokens.kt
index 9270c7e..34be029 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailExpandedTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailExpandedTokens.kt
@@ -13,13 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// VERSION: v0_11_0
// GENERATED CODE - DO NOT MODIFY BY HAND
-// TO MODIFY THIS FILE UPDATE:
-// ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
-//
-// Design System: sandbox
-// Version: v0_9_0
-// Audience: 1p
package androidx.compose.material3.tokens
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailHorizontalItemTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailHorizontalItemTokens.kt
index 1c8d76a..9261d3a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailHorizontalItemTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailHorizontalItemTokens.kt
@@ -13,13 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// VERSION: v0_11_0
// GENERATED CODE - DO NOT MODIFY BY HAND
-// TO MODIFY THIS FILE UPDATE:
-// ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
-//
-// Design System: sandbox
-// Version: v0_9_0
-// Audience: 1p
package androidx.compose.material3.tokens
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailVerticalItemTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailVerticalItemTokens.kt
index a87af2b..c398fc6 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailVerticalItemTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationRailVerticalItemTokens.kt
@@ -13,13 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// VERSION: v0_11_0
// GENERATED CODE - DO NOT MODIFY BY HAND
-// TO MODIFY THIS FILE UPDATE:
-// ux/material/dsdb/contrib/generators/tokens/src/compose/templates/component.jinja2
-//
-// Design System: sandbox
-// Version: v0_9_0
-// Audience: 1p
package androidx.compose.material3.tokens
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
index 749a2aa..8b85857 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
@@ -14,16 +14,22 @@
* limitations under the License.
*/
-// VERSION: v0_5_0
+// VERSION: v0_14_0
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
+import androidx.compose.material3.ShapeDefaults
import androidx.compose.ui.unit.dp
internal object SplitButtonSmallTokens {
val BetweenSpace = 2.0.dp
val ContainerHeight = 40.0.dp
val ContainerShape = ShapeKeyTokens.CornerFull
- val InnerCornerShape = ShapeTokens.CornerExtraSmall
+ val InnerCornerSize = ShapeDefaults.CornerExtraSmall
+ val LeadingButtonLeadingSpace = 16.0.dp
+ val LeadingButtonTrailingSpace = 12.0.dp
+ val TrailingIconSize = 22.0.dp
+ val TrailingButtonLeadingSpace = 13.0.dp
+ val TrailingButtonTrailingSpace = 13.0.dp
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/StandardMotionTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/StandardMotionTokens.kt
new file mode 100644
index 0000000..19db205
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/StandardMotionTokens.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 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.
+ */
+// VERSION: v0_14_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
+package androidx.compose.material3.tokens
+internal object StandardMotionTokens {
+ val SpringDefaultSpatialDamping = 0.9f
+ val SpringDefaultSpatialStiffness = 700.0f
+ val SpringDefaultEffectsDamping = 1.0f
+ val SpringDefaultEffectsStiffness = 1600.0f
+ val SpringFastSpatialDamping = 0.9f
+ val SpringFastSpatialStiffness = 1400.0f
+ val SpringFastEffectsDamping = 1.0f
+ val SpringFastEffectsStiffness = 3800.0f
+ val SpringSlowSpatialDamping = 0.9f
+ val SpringSlowSpatialStiffness = 300.0f
+ val SpringSlowEffectsDamping = 1.0f
+ val SpringSlowEffectsStiffness = 800.0f
+}
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index dffb7ff..110569b 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -151,7 +151,7 @@
method @SuppressCompatibility @androidx.compose.runtime.InternalComposeApi public void recordSideEffect(kotlin.jvm.functions.Function0<kotlin.Unit> effect);
method @SuppressCompatibility @androidx.compose.runtime.InternalComposeApi public void recordUsed(androidx.compose.runtime.RecomposeScope scope);
method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
- method @SuppressCompatibility @androidx.compose.runtime.InternalComposeApi public boolean shouldExecute(boolean parametersChanged);
+ method @SuppressCompatibility @androidx.compose.runtime.InternalComposeApi public boolean shouldExecute(boolean parametersChanged, int flags);
method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
method public void sourceInformation(String sourceInformation);
@@ -1058,6 +1058,30 @@
}
+package androidx.compose.runtime.snapshots.tooling {
+
+ @SuppressCompatibility @androidx.compose.runtime.ExperimentalComposeRuntimeApi public final class SnapshotInstanceObservers {
+ ctor public SnapshotInstanceObservers();
+ ctor public SnapshotInstanceObservers(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver, optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver);
+ method public kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? getReadObserver();
+ method public kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? getWriteObserver();
+ property public final kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver;
+ property public final kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver;
+ }
+
+ @SuppressCompatibility @androidx.compose.runtime.ExperimentalComposeRuntimeApi public interface SnapshotObserver {
+ method public default void onApplied(androidx.compose.runtime.snapshots.Snapshot snapshot, java.util.Set<?> changed);
+ method public default void onCreated(androidx.compose.runtime.snapshots.Snapshot snapshot, androidx.compose.runtime.snapshots.Snapshot? parent, androidx.compose.runtime.snapshots.tooling.SnapshotInstanceObservers? observers);
+ method public default androidx.compose.runtime.snapshots.tooling.SnapshotInstanceObservers? onCreating(androidx.compose.runtime.snapshots.Snapshot? parent, boolean readonly);
+ method public default void onDisposing(androidx.compose.runtime.snapshots.Snapshot snapshot);
+ }
+
+ public final class SnapshotObserverKt {
+ method @SuppressCompatibility @androidx.compose.runtime.ExperimentalComposeRuntimeApi public static androidx.compose.runtime.snapshots.ObserverHandle observeSnapshots(androidx.compose.runtime.snapshots.Snapshot.Companion, androidx.compose.runtime.snapshots.tooling.SnapshotObserver snapshotObserver);
+ }
+
+}
+
package androidx.compose.runtime.tooling {
public interface CompositionData {
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 9c343f0..186ecc4 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -156,7 +156,7 @@
method @SuppressCompatibility @androidx.compose.runtime.InternalComposeApi public void recordSideEffect(kotlin.jvm.functions.Function0<kotlin.Unit> effect);
method @SuppressCompatibility @androidx.compose.runtime.InternalComposeApi public void recordUsed(androidx.compose.runtime.RecomposeScope scope);
method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
- method @SuppressCompatibility @androidx.compose.runtime.InternalComposeApi public boolean shouldExecute(boolean parametersChanged);
+ method @SuppressCompatibility @androidx.compose.runtime.InternalComposeApi public boolean shouldExecute(boolean parametersChanged, int flags);
method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
method public void sourceInformation(String sourceInformation);
@@ -1109,6 +1109,30 @@
}
+package androidx.compose.runtime.snapshots.tooling {
+
+ @SuppressCompatibility @androidx.compose.runtime.ExperimentalComposeRuntimeApi public final class SnapshotInstanceObservers {
+ ctor public SnapshotInstanceObservers();
+ ctor public SnapshotInstanceObservers(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver, optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver);
+ method public kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? getReadObserver();
+ method public kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? getWriteObserver();
+ property public final kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver;
+ property public final kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver;
+ }
+
+ @SuppressCompatibility @androidx.compose.runtime.ExperimentalComposeRuntimeApi public interface SnapshotObserver {
+ method public default void onApplied(androidx.compose.runtime.snapshots.Snapshot snapshot, java.util.Set<?> changed);
+ method public default void onCreated(androidx.compose.runtime.snapshots.Snapshot snapshot, androidx.compose.runtime.snapshots.Snapshot? parent, androidx.compose.runtime.snapshots.tooling.SnapshotInstanceObservers? observers);
+ method public default androidx.compose.runtime.snapshots.tooling.SnapshotInstanceObservers? onCreating(androidx.compose.runtime.snapshots.Snapshot? parent, boolean readonly);
+ method public default void onDisposing(androidx.compose.runtime.snapshots.Snapshot snapshot);
+ }
+
+ public final class SnapshotObserverKt {
+ method @SuppressCompatibility @androidx.compose.runtime.ExperimentalComposeRuntimeApi public static androidx.compose.runtime.snapshots.ObserverHandle observeSnapshots(androidx.compose.runtime.snapshots.Snapshot.Companion, androidx.compose.runtime.snapshots.tooling.SnapshotObserver snapshotObserver);
+ }
+
+}
+
package androidx.compose.runtime.tooling {
public interface CompositionData {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 0e48726..28f2850 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -985,7 +985,7 @@
* execute (such as its scope was invalidated or a static composition local it was changed) or
* the composition is pausable and the composition is pausing.
*/
- @InternalComposeApi fun shouldExecute(parametersChanged: Boolean): Boolean
+ @InternalComposeApi fun shouldExecute(parametersChanged: Boolean, flags: Int): Boolean
// Internal API
@@ -3038,7 +3038,8 @@
}
@ComposeCompilerApi
- override fun shouldExecute(parametersChanged: Boolean): Boolean {
+ @Suppress("UNUSED")
+ override fun shouldExecute(parametersChanged: Boolean, flags: Int): Boolean {
return parametersChanged || !skipping
}
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 e5cea89..e593c27 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
@@ -20,6 +20,7 @@
import androidx.collection.mutableScatterSetOf
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisallowComposableCalls
+import androidx.compose.runtime.ExperimentalComposeRuntimeApi
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.SynchronizedObject
import androidx.compose.runtime.checkPrecondition
@@ -32,6 +33,9 @@
import androidx.compose.runtime.requirePrecondition
import androidx.compose.runtime.snapshots.Snapshot.Companion.takeMutableSnapshot
import androidx.compose.runtime.snapshots.Snapshot.Companion.takeSnapshot
+import androidx.compose.runtime.snapshots.tooling.creatingSnapshot
+import androidx.compose.runtime.snapshots.tooling.dispatchObserverOnApplied
+import androidx.compose.runtime.snapshots.tooling.dispatchObserverOnDispose
import androidx.compose.runtime.synchronized
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
@@ -740,25 +744,30 @@
* with this snapshot can be collected. Nested active snapshots are still valid after the parent
* has been disposed but calling [apply] will fail.
*/
+ @OptIn(ExperimentalComposeRuntimeApi::class)
open fun takeNestedMutableSnapshot(
readObserver: ((Any) -> Unit)? = null,
writeObserver: ((Any) -> Unit)? = null
): MutableSnapshot {
validateNotDisposed()
validateNotAppliedOrPinned()
- return advance {
- sync {
- val newId = nextSnapshotId++
- openSnapshots = openSnapshots.set(newId)
- val currentInvalid = invalid
- this.invalid = currentInvalid.set(newId)
- NestedMutableSnapshot(
- newId,
- currentInvalid.addRange(id + 1, newId),
- mergedReadObserver(readObserver, this.readObserver),
- mergedWriteObserver(writeObserver, this.writeObserver),
- this
- )
+ return creatingSnapshot(this, readObserver, writeObserver, readonly = false) {
+ actualReadObserver,
+ actualWriteObserver ->
+ advance {
+ sync {
+ val newId = nextSnapshotId++
+ openSnapshots = openSnapshots.set(newId)
+ val currentInvalid = invalid
+ this.invalid = currentInvalid.set(newId)
+ NestedMutableSnapshot(
+ newId,
+ currentInvalid.addRange(id + 1, newId),
+ mergedReadObserver(actualReadObserver, this.readObserver),
+ mergedWriteObserver(actualWriteObserver, this.writeObserver),
+ this
+ )
+ }
}
}
}
@@ -853,6 +862,8 @@
observers.fastForEach { it(modifiedSet, this) }
}
+ dispatchObserverOnApplied(this, modified)
+
// 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.
@@ -878,23 +889,32 @@
if (!disposed) {
super.dispose()
nestedDeactivated(this)
+ dispatchObserverOnDispose(this)
}
}
+ @OptIn(ExperimentalComposeRuntimeApi::class)
override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot {
validateNotDisposed()
validateNotAppliedOrPinned()
val previousId = id
- return advance {
- sync {
- val readonlyId = nextSnapshotId++
- openSnapshots = openSnapshots.set(readonlyId)
- NestedReadonlySnapshot(
- id = readonlyId,
- invalid = invalid.addRange(previousId + 1, readonlyId),
- readObserver = mergedReadObserver(readObserver, this.readObserver),
- parent = this
- )
+ return creatingSnapshot(
+ if (this is GlobalSnapshot) null else this,
+ readObserver = readObserver,
+ writeObserver = null,
+ readonly = true
+ ) { actualReadObserver, _ ->
+ advance {
+ sync {
+ val readonlyId = nextSnapshotId++
+ openSnapshots = openSnapshots.set(readonlyId)
+ NestedReadonlySnapshot(
+ id = readonlyId,
+ invalid = invalid.addRange(previousId + 1, readonlyId),
+ readObserver = mergedReadObserver(actualReadObserver, this.readObserver),
+ parent = this
+ )
+ }
}
}
}
@@ -1298,14 +1318,22 @@
get() = null
@Suppress("UNUSED_PARAMETER") set(value) = unsupported()
+ @OptIn(ExperimentalComposeRuntimeApi::class)
override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot {
validateOpen(this)
- return NestedReadonlySnapshot(
- id = id,
- invalid = invalid,
- readObserver = mergedReadObserver(readObserver, this.readObserver),
- parent = this
- )
+ return creatingSnapshot(
+ parent = this,
+ readObserver = readObserver,
+ writeObserver = null,
+ readonly = true,
+ ) { actualReadObserver, _ ->
+ NestedReadonlySnapshot(
+ id = id,
+ invalid = invalid,
+ readObserver = mergedReadObserver(actualReadObserver, this.readObserver),
+ parent = this
+ )
+ }
}
override fun notifyObjectsInitialized() {
@@ -1316,6 +1344,7 @@
if (!disposed) {
nestedDeactivated(this)
super.dispose()
+ dispatchObserverOnDispose(this)
}
}
@@ -1351,13 +1380,21 @@
override val root: Snapshot
get() = parent.root
+ @OptIn(ExperimentalComposeRuntimeApi::class)
override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?) =
- NestedReadonlySnapshot(
- id = id,
- invalid = invalid,
- readObserver = mergedReadObserver(readObserver, this.readObserver),
- parent = parent
- )
+ creatingSnapshot(
+ parent = this,
+ readObserver = readObserver,
+ writeObserver = null,
+ readonly = true,
+ ) { actualReadObserver, _ ->
+ NestedReadonlySnapshot(
+ id = id,
+ invalid = invalid,
+ readObserver = mergedReadObserver(actualReadObserver, this.readObserver),
+ parent = parent
+ )
+ }
override fun notifyObjectsInitialized() {
// Nothing to do for read-only snapshots
@@ -1372,6 +1409,7 @@
}
parent.nestedDeactivated(this)
super.dispose()
+ dispatchObserverOnDispose(this)
}
}
@@ -1405,32 +1443,49 @@
}
) {
+ @OptIn(ExperimentalComposeRuntimeApi::class)
override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot =
- takeNewSnapshot { invalid ->
- ReadonlySnapshot(
- id = sync { nextSnapshotId++ },
- invalid = invalid,
- readObserver = readObserver
- )
+ creatingSnapshot(
+ parent = null,
+ readonly = true,
+ readObserver = readObserver,
+ writeObserver = null,
+ ) { actualReadObserver, _ ->
+ takeNewSnapshot { invalid ->
+ ReadonlySnapshot(
+ id = sync { nextSnapshotId++ },
+ invalid = invalid,
+ readObserver = actualReadObserver
+ )
+ }
}
+ @OptIn(ExperimentalComposeRuntimeApi::class)
override fun takeNestedMutableSnapshot(
readObserver: ((Any) -> Unit)?,
writeObserver: ((Any) -> Unit)?
- ): MutableSnapshot = takeNewSnapshot { invalid ->
- MutableSnapshot(
- id = sync { nextSnapshotId++ },
- invalid = invalid,
-
- // It is intentional that the global read observers are not merged with mutable
- // snapshots read observers.
+ ): MutableSnapshot =
+ creatingSnapshot(
+ parent = null,
+ readonly = false,
readObserver = readObserver,
-
- // It is intentional that global write observers are not merged with mutable
- // snapshots write observers.
writeObserver = writeObserver
- )
- }
+ ) { actualReadObserver, actualWriteObserver ->
+ takeNewSnapshot { invalid ->
+ MutableSnapshot(
+ id = sync { nextSnapshotId++ },
+ invalid = invalid,
+
+ // It is intentional that the global read observers are not merged with mutable
+ // snapshots read observers.
+ readObserver = actualReadObserver,
+
+ // It is intentional that global write observers are not merged with mutable
+ // snapshots write observers.
+ writeObserver = actualWriteObserver
+ )
+ }
+ }
override fun notifyObjectsInitialized() {
advanceGlobalSnapshot()
@@ -1519,6 +1574,7 @@
applied = true
deactivate()
+ dispatchObserverOnApplied(this, modified)
return SnapshotApplyResult.Success
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/tooling/SnapshotObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/tooling/SnapshotObserver.kt
new file mode 100644
index 0000000..489e858
--- /dev/null
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/tooling/SnapshotObserver.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime.snapshots.tooling
+
+import androidx.collection.ScatterSet
+import androidx.compose.runtime.ExperimentalComposeRuntimeApi
+import androidx.compose.runtime.collection.wrapIntoSet
+import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentList
+import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentListOf
+import androidx.compose.runtime.snapshots.ObserverHandle
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.snapshots.StateObject
+import androidx.compose.runtime.snapshots.fastForEach
+import androidx.compose.runtime.snapshots.sync
+
+/**
+ * An observer for the snapshot system that notifies an observer when a snapshot is created,
+ * applied, and/or disposed.
+ *
+ * All methods are called in the thread of the snapshot so all observers must be thread safe as they
+ * may be called from any thread.
+ *
+ * Calling any of the Snapshot API (including, reading or writing mutable state objects) is not
+ * supported and may produce inconsistent result or throw an exception.
+ */
+@ExperimentalComposeRuntimeApi
+@Suppress("CallbackName")
+interface SnapshotObserver {
+ /**
+ * Called before a snapshot is created allowing reads and writes to the snapshot to be observed.
+ *
+ * This method is called in the same thread that creates the snapshot.
+ *
+ * @param parent the parent snapshot for the new snapshot if it is a nested snapshot or null
+ * otherwise.
+ * @param readonly whether the snapshot being created will be read-only.
+ * @return optional read and write observers that will be added to the snapshot created.
+ */
+ fun onCreating(parent: Snapshot?, readonly: Boolean): SnapshotInstanceObservers? = null
+
+ /**
+ * Called after snapshot is created.
+ *
+ * This is called prior to the instance being returned by [Snapshot.takeSnapshot] or
+ * [Snapshot.takeMutableSnapshot].
+ *
+ * This method is called in the same thread that creates the snapshot.
+ *
+ * @param snapshot the snapshot that was created.
+ * @param parent the parent snapshot for the new snapshot if it is a nested snapshot or null if
+ * it is a root snapshot.
+ * @param observers the read and write observers that were installed by the value returned by
+ * [onCreated]. This allows correlating which snapshot observers returned by [onCreating] to
+ * the [snapshot] that was created.
+ */
+ fun onCreated(snapshot: Snapshot, parent: Snapshot?, observers: SnapshotInstanceObservers?) {}
+
+ /**
+ * Called while a snapshot is being disposed.
+ *
+ * This method is called in the same thread that disposes the snapshot.
+ *
+ * @param snapshot information about the snapshot that was created.
+ */
+ fun onDisposing(snapshot: Snapshot) {}
+
+ /**
+ * Called after a snapshot is applied.
+ *
+ * For nested snapshots, the changes will only be visible to the parent snapshot, not globally.
+ * Snapshots do not have a parent will have changes that are visible globally and such
+ * notification are equivalent the notification sent to [Snapshot.registerApplyObserver] and
+ * will include all objects modified by any nested snapshots that have been applied to the
+ * parent snapshot.
+ *
+ * This method is called in the same thread that applies the snapshot.
+ *
+ * @param snapshot the snapshot that was applied.
+ * @param changed the set of objects that were modified during the snapshot.
+ */
+ fun onApplied(snapshot: Snapshot, changed: Set<Any>) {}
+}
+
+/**
+ * The return result of [SnapshotObserver.onCreating] allowing the reads and writes performed in the
+ * newly created snapshot to be observed
+ */
+@ExperimentalComposeRuntimeApi
+class SnapshotInstanceObservers(
+ /**
+ * Called whenever a state is read in the snapshot. This is called before the read observer
+ * passed to [Snapshot.takeSnapshot] or [Snapshot.takeMutableSnapshot].
+ *
+ * This method is called in the same thread that reads snapshot state.
+ */
+ val readObserver: ((Any) -> Unit)? = null,
+
+ /**
+ * Called just before a state object is written to the first time in the snapshot or a nested
+ * mutable snapshot. This might be called several times for the same object if nested mutable
+ * snapshots are created as the unmodified value may be needed by the nested snapshot so a new
+ * copy is created. This is not called for each write, only when the write results in the object
+ * be recorded as being modified requiring a copy to be made before the write completes. This is
+ * called before the write has been applied to the instance.
+ *
+ * This is called before the write observer passed to [Snapshot.takeMutableSnapshot].
+ *
+ * This method is called in the same thread that writes to the snapshot state.
+ */
+ val writeObserver: ((Any) -> Unit)? = null,
+)
+
+/**
+ * This is a tooling API and is not intended to be used in a production application as it will
+ * introduce global overhead to creating, applying and disposing all snapshots and, potentially, to
+ * reading and writing all state objects.
+ *
+ * Observe when snapshots are created, applied, and/or disposed. The observer can also install read
+ * and write observers on the snapshot being created.
+ *
+ * This method is thread-safe and calling [ObserverHandle.dispose] on the [ObserverHandle] returned
+ * is also thread-safe.
+ *
+ * @param snapshotObserver the snapshot observer to install.
+ * @return [ObserverHandle] an instance to unregister the [snapshotObserver].
+ */
+@ExperimentalComposeRuntimeApi
+fun Snapshot.Companion.observeSnapshots(snapshotObserver: SnapshotObserver): ObserverHandle {
+ sync { observers = (observers ?: persistentListOf()).add(snapshotObserver) }
+ return ObserverHandle {
+ sync {
+ val newObservers = observers?.remove(snapshotObserver)
+ observers = newObservers?.takeIf { it.isNotEmpty() }
+ }
+ }
+}
+
+@ExperimentalComposeRuntimeApi private var observers: PersistentList<SnapshotObserver>? = null
+
+@ExperimentalComposeRuntimeApi
+internal inline fun <R : Snapshot> creatingSnapshot(
+ parent: Snapshot?,
+ noinline readObserver: ((Any) -> Unit)?,
+ noinline writeObserver: ((Any) -> Unit)?,
+ readonly: Boolean,
+ crossinline block: (readObserver: ((Any) -> Unit)?, writeObserver: ((Any) -> Unit)?) -> R
+): R {
+ var observerMap: Map<SnapshotObserver, SnapshotInstanceObservers>? = null
+ val observers = observers
+ var actualReadObserver = readObserver
+ var actualWriteObserver = writeObserver
+ if (observers != null) {
+ val result = observers.mergeObservers(parent, readonly, readObserver, writeObserver)
+ val mappedObservers = result.first
+ actualReadObserver = mappedObservers.readObserver
+ actualWriteObserver = mappedObservers.writeObserver
+ observerMap = result.second
+ }
+ val result = block(actualReadObserver, actualWriteObserver)
+ observers?.dispatchCreatedObservers(parent, result, observerMap)
+ return result
+}
+
+@ExperimentalComposeRuntimeApi
+internal fun PersistentList<SnapshotObserver>.mergeObservers(
+ parent: Snapshot?,
+ readonly: Boolean,
+ readObserver: ((Any) -> Unit)?,
+ writeObserver: ((Any) -> Unit)?,
+): Pair<SnapshotInstanceObservers, Map<SnapshotObserver, SnapshotInstanceObservers>?> {
+ var currentReadObserver = readObserver
+ var currentWriteObserver = writeObserver
+ var observerMap: MutableMap<SnapshotObserver, SnapshotInstanceObservers>? = null
+ fastForEach { observer ->
+ val instance = observer.onCreating(parent, readonly)
+ if (instance != null) {
+ currentReadObserver = mergeObservers(instance.readObserver, currentReadObserver)
+ currentWriteObserver = mergeObservers(instance.writeObserver, currentWriteObserver)
+ (observerMap
+ ?: run {
+ val newMap = mutableMapOf<SnapshotObserver, SnapshotInstanceObservers>()
+ observerMap = newMap
+ newMap
+ })[observer] = instance
+ }
+ }
+ return SnapshotInstanceObservers(currentReadObserver, currentWriteObserver) to observerMap
+}
+
+private fun mergeObservers(a: ((Any) -> Unit)?, b: ((Any) -> Unit)?): ((Any) -> Unit)? {
+ return if (a != null && b != null) {
+ {
+ a(it)
+ b(it)
+ }
+ } else a ?: b
+}
+
+@ExperimentalComposeRuntimeApi
+internal fun PersistentList<SnapshotObserver>.dispatchCreatedObservers(
+ parent: Snapshot?,
+ result: Snapshot,
+ observerMap: Map<SnapshotObserver, SnapshotInstanceObservers>?
+) {
+ fastForEach { observer ->
+ val instance = observerMap?.get(observer)
+ observer.onCreated(result, parent, instance)
+ }
+}
+
+@OptIn(ExperimentalComposeRuntimeApi::class)
+internal fun dispatchObserverOnDispose(snapshot: Snapshot) {
+ observers?.fastForEach { observer -> observer.onDisposing(snapshot) }
+}
+
+@OptIn(ExperimentalComposeRuntimeApi::class)
+internal fun dispatchObserverOnApplied(snapshot: Snapshot, changes: ScatterSet<StateObject>?) {
+ val observers = observers
+ if (!observers.isNullOrEmpty()) {
+ val wrappedChanges = changes?.wrapIntoSet() ?: emptySet()
+ observers.fastForEach { observer -> observer.onApplied(snapshot, wrappedChanges) }
+ }
+}
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/tooling/SnapshotObserverTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/tooling/SnapshotObserverTests.kt
new file mode 100644
index 0000000..96a947c
--- /dev/null
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/tooling/SnapshotObserverTests.kt
@@ -0,0 +1,468 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalComposeRuntimeApi::class)
+
+package androidx.compose.runtime.snapshots.tooling
+
+import androidx.collection.mutableScatterSetOf
+import androidx.compose.runtime.ExperimentalComposeRuntimeApi
+import androidx.compose.runtime.InternalComposeApi
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.snapshots.Snapshot.Companion.openSnapshotCount
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class SnapshotObserverTests {
+ @Test
+ fun canAddAndRemoveObserver() {
+ observeSnapshots(object : SnapshotObserver {}) {}
+ }
+
+ @Test
+ fun canObserverReadonlySnapshotCreation() {
+ val observed = mutableScatterSetOf<Snapshot>()
+ observeSnapshots(
+ object : SnapshotObserver {
+ override fun onCreated(
+ snapshot: Snapshot,
+ parent: Snapshot?,
+ observers: SnapshotInstanceObservers?
+ ) {
+ observed.add(snapshot)
+ }
+ }
+ ) {
+ val created = Snapshot.takeSnapshot()
+ assertTrue(created in observed)
+ created.dispose()
+ }
+ }
+
+ @Test
+ fun canObserverMutableSnapshotCreation() {
+ val observed = mutableScatterSetOf<Snapshot>()
+ observeSnapshots(
+ object : SnapshotObserver {
+ override fun onCreated(
+ snapshot: Snapshot,
+ parent: Snapshot?,
+ observers: SnapshotInstanceObservers?
+ ) {
+ observed.add(snapshot)
+ }
+ }
+ ) {
+ val created = Snapshot.takeMutableSnapshot()
+ assertTrue(created in observed)
+ created.dispose()
+ }
+ }
+
+ @Test
+ fun canObserveApply() {
+ val applied = mutableListOf<Pair<Snapshot, Set<Any>>>()
+ observeSnapshots(
+ object : SnapshotObserver {
+ override fun onApplied(snapshot: Snapshot, changed: Set<Any>) {
+ applied.add(snapshot to changed)
+ }
+ }
+ ) {
+ val state = mutableIntStateOf(10)
+ val snapshot = Snapshot.takeMutableSnapshot()
+ snapshot.enter { state.value = 12 }
+ snapshot.apply().check()
+ val apply = applied.first()
+ assertEquals(snapshot, apply.first)
+ assertTrue(apply.second.contains(state))
+ snapshot.dispose()
+ }
+ }
+
+ @Test
+ fun canObserverDisposeOfReadonlySnapshot() {
+ val disposed = mutableScatterSetOf<Snapshot>()
+ observeSnapshots(
+ object : SnapshotObserver {
+ override fun onDisposing(snapshot: Snapshot) {
+ disposed.add(snapshot)
+ }
+ }
+ ) {
+ val snapshot = Snapshot.takeSnapshot()
+ snapshot.dispose()
+ assertTrue(disposed.contains(snapshot))
+ }
+ }
+
+ @Test
+ fun canObserverDisposeOfMutableSnapshot_NotApplied() {
+ val disposed = mutableScatterSetOf<Snapshot>()
+ observeSnapshots(
+ object : SnapshotObserver {
+ override fun onDisposing(snapshot: Snapshot) {
+ disposed.add(snapshot)
+ }
+ }
+ ) {
+ val snapshot = Snapshot.takeMutableSnapshot()
+ snapshot.dispose()
+ assertTrue(disposed.contains(snapshot))
+ }
+ }
+
+ @Test
+ fun canObserverDisposeOfMutableSnapshot_Applied() {
+ val disposed = mutableScatterSetOf<Snapshot>()
+ observeSnapshots(
+ object : SnapshotObserver {
+ override fun onDisposing(snapshot: Snapshot) {
+ disposed.add(snapshot)
+ }
+ }
+ ) {
+ val snapshot = Snapshot.takeMutableSnapshot()
+ snapshot.apply().check()
+ snapshot.dispose()
+ assertTrue(disposed.contains(snapshot))
+ }
+ }
+
+ @Test
+ fun canObserveApplyOfNestedSnapshot() {
+ val applied = mutableListOf<Pair<Snapshot, Set<Any>>>()
+ observeSnapshots(
+ object : SnapshotObserver {
+ override fun onApplied(snapshot: Snapshot, changed: Set<Any>) {
+ applied.add(snapshot to changed)
+ }
+ }
+ ) {
+ val state = mutableIntStateOf(10)
+ val snapshot = Snapshot.takeMutableSnapshot()
+ val nestedSnapshot = snapshot.takeNestedMutableSnapshot()
+ nestedSnapshot.enter { state.value = 12 }
+ nestedSnapshot.apply().check()
+ snapshot.apply().check()
+
+ val nestedApply = applied.first()
+ assertEquals(nestedSnapshot, nestedApply.first)
+ assertTrue(nestedApply.second.contains(state))
+ nestedSnapshot.dispose()
+ val snapshotApply = applied.last()
+ assertEquals(snapshot, snapshotApply.first)
+ assertTrue(snapshotApply.second.contains(state))
+ snapshot.dispose()
+ }
+ }
+
+ @Test
+ fun canObserveReadsInReadonlySnapshot() {
+ val state = mutableIntStateOf(10)
+ val read = mutableListOf<Pair<Any, Boolean>>()
+
+ observeSnapshots(
+ object : SnapshotObserver {
+ override fun onCreating(
+ parent: Snapshot?,
+ readonly: Boolean
+ ): SnapshotInstanceObservers {
+ return SnapshotInstanceObservers(readObserver = { read.add(it to true) })
+ }
+ }
+ ) {
+ val snapshot = Snapshot.takeSnapshot(readObserver = { read.add(it to false) })
+ try {
+ val result = snapshot.enter { state.value }
+ assertEquals(10, result)
+ assertEquals(mutableListOf<Pair<Any, Boolean>>(state to true, state to false), read)
+ } finally {
+ snapshot.dispose()
+ }
+ }
+ }
+
+ @Test
+ fun canObserveReadsInMutableSnapshot() {
+ val state = mutableIntStateOf(10)
+ val read = mutableListOf<Pair<Any, Boolean>>()
+
+ observeSnapshots(
+ object : SnapshotObserver {
+ override fun onCreating(
+ parent: Snapshot?,
+ readonly: Boolean
+ ): SnapshotInstanceObservers {
+ return SnapshotInstanceObservers(readObserver = { read.add(it to true) })
+ }
+ }
+ ) {
+ val snapshot = Snapshot.takeMutableSnapshot(readObserver = { read.add(it to false) })
+ try {
+ val result = snapshot.enter { state.value }
+ assertEquals(10, result)
+ assertEquals(mutableListOf<Pair<Any, Boolean>>(state to true, state to false), read)
+ } finally {
+ snapshot.dispose()
+ }
+ }
+ }
+
+ @Test
+ fun canObserveWrites() {
+ val state = mutableIntStateOf(10)
+ val writes = mutableListOf<Pair<Any, Boolean>>()
+ observeSnapshots(
+ object : SnapshotObserver {
+ override fun onCreating(
+ parent: Snapshot?,
+ readonly: Boolean
+ ): SnapshotInstanceObservers {
+ return SnapshotInstanceObservers(writeObserver = { writes.add(it to true) })
+ }
+ }
+ ) {
+ val snapshot = Snapshot.takeMutableSnapshot(writeObserver = { writes.add(it to false) })
+ try {
+ val result =
+ snapshot.enter {
+ state.value = 20
+ state.value
+ }
+ assertEquals(20, result)
+ assertEquals(
+ expected = mutableListOf<Pair<Any, Boolean>>(state to true, state to false),
+ actual = writes
+ )
+ } finally {
+ snapshot.dispose()
+ }
+ }
+ }
+
+ @Test
+ fun canHaveMultipleObservers() {
+ val events = mutableListOf<Pair<Any?, String>>()
+ fun observer(prefix: String) =
+ object : SnapshotObserver {
+ override fun onCreating(
+ parent: Snapshot?,
+ readonly: Boolean
+ ): SnapshotInstanceObservers {
+ record(parent, "creating, readonly = $readonly")
+ return SnapshotInstanceObservers(
+ readObserver = { record(it, "reading") },
+ writeObserver = { record(it, "writing") }
+ )
+ }
+
+ override fun onCreated(
+ snapshot: Snapshot,
+ parent: Snapshot?,
+ observers: SnapshotInstanceObservers?
+ ) {
+ record(snapshot to parent, "created")
+ }
+
+ override fun onDisposing(snapshot: Snapshot) {
+ record(snapshot, "disposing")
+ }
+
+ override fun onApplied(snapshot: Snapshot, changed: Set<Any>) {
+ record(snapshot to changed, "applied")
+ }
+
+ fun record(value: Any?, msg: String) {
+ events.add(value to "$prefix: $msg")
+ }
+ }
+ val state1 = mutableIntStateOf(1)
+ val state2 = mutableIntStateOf(2)
+
+ observeSnapshots(observer("Outer")) {
+ observeSnapshots(observer("Inner")) {
+ val ros1 = Snapshot.takeSnapshot()
+ try {
+ ros1.enter {
+ state1.value
+ state2.value
+ }
+ } finally {
+ ros1.dispose()
+ }
+
+ val ms1 = Snapshot.takeMutableSnapshot()
+ try {
+ ms1.enter { state1.value = 11 }
+ ms1.apply().check()
+ } finally {
+ ms1.dispose()
+ }
+ assertEquals(
+ listOf(
+ null to "Outer: creating, readonly = true",
+ null to "Inner: creating, readonly = true",
+ (ros1 to null) to "Outer: created",
+ (ros1 to null) to "Inner: created",
+ state1 to "Inner: reading",
+ state1 to "Outer: reading",
+ state2 to "Inner: reading",
+ state2 to "Outer: reading",
+ ros1 to "Outer: disposing",
+ ros1 to "Inner: disposing",
+ null to "Outer: creating, readonly = false",
+ null to "Inner: creating, readonly = false",
+ (ms1 to null) to "Outer: created",
+ (ms1 to null) to "Inner: created",
+ state1 to "Inner: writing",
+ state1 to "Outer: writing",
+ (ms1 to setOf(state1)) to "Outer: applied",
+ (ms1 to setOf(state1)) to "Inner: applied",
+ ms1 to "Outer: disposing",
+ ms1 to "Inner: disposing"
+ ),
+ events as List<*>
+ )
+ }
+ }
+ }
+
+ @Test
+ fun receivesTheCorrectParent() {
+ val events = mutableListOf<Pair<Any?, String>>()
+ fun observer() =
+ object : SnapshotObserver {
+ override fun onCreating(
+ parent: Snapshot?,
+ readonly: Boolean
+ ): SnapshotInstanceObservers {
+ record(parent, "creating, readonly = $readonly")
+ return SnapshotInstanceObservers(
+ readObserver = { record(it, "reading") },
+ writeObserver = { record(it, "writing") }
+ )
+ }
+
+ override fun onCreated(
+ snapshot: Snapshot,
+ parent: Snapshot?,
+ observers: SnapshotInstanceObservers?
+ ) {
+ record(snapshot to parent, "created")
+ }
+
+ override fun onDisposing(snapshot: Snapshot) {
+ record(snapshot, "disposing")
+ }
+
+ override fun onApplied(snapshot: Snapshot, changed: Set<Any>) {
+ record(snapshot to changed, "applied")
+ }
+
+ fun record(value: Any?, msg: String) {
+ events.add(value to msg)
+ }
+ }
+
+ observeSnapshots(observer()) {
+ val ro1 = Snapshot.takeSnapshot()
+ val ro2 = ro1.takeNestedSnapshot()
+ ro2.dispose()
+ ro1.dispose()
+
+ val ms1 = Snapshot.takeMutableSnapshot()
+ val ms2 = ms1.takeNestedMutableSnapshot()
+ ms1.dispose()
+ ms2.dispose()
+
+ assertEquals(
+ listOf(
+ null to "creating, readonly = true",
+ (ro1 to null) to "created",
+ ro1 to "creating, readonly = true",
+ (ro2 to ro1) to "created",
+ ro2 to "disposing",
+ ro1 to "disposing",
+ null to "creating, readonly = false",
+ (ms1 to null) to "created",
+ ms1 to "creating, readonly = false",
+ (ms2 to ms1) to "created",
+ ms1 to "disposing",
+ ms2 to "disposing",
+ ),
+ events
+ )
+ }
+ }
+
+ @Test
+ fun canCorrelateCreatingAndCreating() {
+ var key: SnapshotInstanceObservers? = null
+ observeSnapshots(
+ object : SnapshotObserver {
+ override fun onCreating(
+ parent: Snapshot?,
+ readonly: Boolean
+ ): SnapshotInstanceObservers {
+ val result = SnapshotInstanceObservers()
+ key = result
+ return result
+ }
+
+ override fun onCreated(
+ snapshot: Snapshot,
+ parent: Snapshot?,
+ observers: SnapshotInstanceObservers?
+ ) {
+ assertEquals(observers, key)
+ }
+ }
+ ) {
+ val snapshot = Snapshot.takeMutableSnapshot()
+ snapshot.dispose()
+ }
+ }
+
+ private var count = 0
+
+ @OptIn(InternalComposeApi::class)
+ @BeforeTest
+ fun recordOpenSnapshots() {
+ count = openSnapshotCount()
+ }
+
+ // Validate that the tests do not change the number of open snapshots
+ @OptIn(InternalComposeApi::class)
+ @AfterTest
+ fun validateOpenSnapshots() {
+ assertEquals(count, openSnapshotCount(), "Snapshot not disposed?")
+ }
+}
+
+@ExperimentalComposeRuntimeApi
+private inline fun <R> observeSnapshots(observer: SnapshotObserver, block: () -> R): R {
+ val handle = Snapshot.observeSnapshots(observer)
+ try {
+ return block()
+ } finally {
+ handle.dispose()
+ }
+}
diff --git a/compose/ui/ui-geometry/api/current.txt b/compose/ui/ui-geometry/api/current.txt
index 9253299..7cfe3bd 100644
--- a/compose/ui/ui-geometry/api/current.txt
+++ b/compose/ui/ui-geometry/api/current.txt
@@ -94,12 +94,14 @@
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class Offset {
+ ctor public Offset(long packedValue);
method @androidx.compose.runtime.Stable public inline operator float component1();
method @androidx.compose.runtime.Stable public inline operator float component2();
method public long copy(optional float x, optional float y);
method @androidx.compose.runtime.Stable public operator long div(float operand);
method @androidx.compose.runtime.Stable public float getDistance();
method @androidx.compose.runtime.Stable public float getDistanceSquared();
+ method public long getPackedValue();
method public inline float getX();
method public inline float getY();
method @androidx.compose.runtime.Stable public inline boolean isValid();
@@ -108,6 +110,7 @@
method @androidx.compose.runtime.Stable public operator long rem(float operand);
method @androidx.compose.runtime.Stable public operator long times(float operand);
method @androidx.compose.runtime.Stable public inline operator long unaryMinus();
+ property public final long packedValue;
property @androidx.compose.runtime.Stable public final inline float x;
property @androidx.compose.runtime.Stable public final inline float y;
field public static final androidx.compose.ui.geometry.Offset.Companion Companion;
@@ -123,7 +126,7 @@
}
public final class OffsetKt {
- method @androidx.compose.runtime.Stable public static long Offset(float x, float y);
+ method @androidx.compose.runtime.Stable public static inline long Offset(float x, float y);
method public static inline boolean isFinite(long);
method public static inline boolean isSpecified(long);
method public static inline boolean isUnspecified(long);
@@ -267,6 +270,7 @@
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class Size {
+ ctor public Size(long packedValue);
method @androidx.compose.runtime.Stable public inline operator float component1();
method @androidx.compose.runtime.Stable public inline operator float component2();
method public long copy(optional float width, optional float height);
@@ -274,12 +278,14 @@
method public inline float getHeight();
method public float getMaxDimension();
method public float getMinDimension();
+ method public long getPackedValue();
method public inline float getWidth();
method @androidx.compose.runtime.Stable public boolean isEmpty();
method @androidx.compose.runtime.Stable public operator long times(float operand);
property @androidx.compose.runtime.Stable public final inline float height;
property @androidx.compose.runtime.Stable public final float maxDimension;
property @androidx.compose.runtime.Stable public final float minDimension;
+ property public final long packedValue;
property @androidx.compose.runtime.Stable public final inline float width;
field public static final androidx.compose.ui.geometry.Size.Companion Companion;
}
diff --git a/compose/ui/ui-geometry/api/restricted_current.txt b/compose/ui/ui-geometry/api/restricted_current.txt
index 4714d67..940e4ba 100644
--- a/compose/ui/ui-geometry/api/restricted_current.txt
+++ b/compose/ui/ui-geometry/api/restricted_current.txt
@@ -105,13 +105,14 @@
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class Offset {
- ctor @kotlin.PublishedApi internal Offset(@kotlin.PublishedApi long packedValue);
+ ctor public Offset(long packedValue);
method @androidx.compose.runtime.Stable public inline operator float component1();
method @androidx.compose.runtime.Stable public inline operator float component2();
method public long copy(optional float x, optional float y);
method @androidx.compose.runtime.Stable public operator long div(float operand);
method @androidx.compose.runtime.Stable public float getDistance();
method @androidx.compose.runtime.Stable public float getDistanceSquared();
+ method public long getPackedValue();
method public inline float getX();
method public inline float getY();
method @androidx.compose.runtime.Stable public inline boolean isValid();
@@ -120,6 +121,7 @@
method @androidx.compose.runtime.Stable public operator long rem(float operand);
method @androidx.compose.runtime.Stable public operator long times(float operand);
method @androidx.compose.runtime.Stable public inline operator long unaryMinus();
+ property public final long packedValue;
property @androidx.compose.runtime.Stable public final inline float x;
property @androidx.compose.runtime.Stable public final inline float y;
field public static final androidx.compose.ui.geometry.Offset.Companion Companion;
@@ -135,7 +137,7 @@
}
public final class OffsetKt {
- method @androidx.compose.runtime.Stable public static long Offset(float x, float y);
+ method @androidx.compose.runtime.Stable public static inline long Offset(float x, float y);
method public static inline boolean isFinite(long);
method public static inline boolean isSpecified(long);
method public static inline boolean isUnspecified(long);
@@ -279,7 +281,7 @@
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class Size {
- ctor @kotlin.PublishedApi internal Size(@kotlin.PublishedApi long packedValue);
+ ctor public Size(long packedValue);
method @androidx.compose.runtime.Stable public inline operator float component1();
method @androidx.compose.runtime.Stable public inline operator float component2();
method public long copy(optional float width, optional float height);
@@ -287,12 +289,14 @@
method public inline float getHeight();
method public float getMaxDimension();
method public float getMinDimension();
+ method public long getPackedValue();
method public inline float getWidth();
method @androidx.compose.runtime.Stable public boolean isEmpty();
method @androidx.compose.runtime.Stable public operator long times(float operand);
property @androidx.compose.runtime.Stable public final inline float height;
property @androidx.compose.runtime.Stable public final float maxDimension;
property @androidx.compose.runtime.Stable public final float minDimension;
+ property public final long packedValue;
property @androidx.compose.runtime.Stable public final inline float width;
field public static final androidx.compose.ui.geometry.Size.Companion Companion;
}
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
index 92ed64e..100212d 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
+
package androidx.compose.ui.geometry
import androidx.compose.runtime.Immutable
@@ -24,8 +26,8 @@
import androidx.compose.ui.util.unpackFloat2
import kotlin.math.sqrt
-/** Constructs an Offset from the given relative x and y offsets */
-@Stable fun Offset(x: Float, y: Float) = Offset(packFloats(x, y))
+/** Constructs an Offset from the given relative [x] and [y] offsets */
+@Stable inline fun Offset(x: Float, y: Float) = Offset(packFloats(x, y))
/**
* An immutable 2D floating-point offset.
@@ -44,15 +46,20 @@
* See also:
* * [Size], which represents a vector describing the size of a rectangle.
*
- * Creates an offset. The first argument sets [x], the horizontal component, and the second sets
- * [y], the vertical component.
+ * To create an [Offset], call the top-level function that accepts an x/y pair of coordinates:
+ * ```
+ * val offset = Offset(x, y)
+ * ```
+ *
+ * The primary constructor of [Offset] is intended to be used with the [packedValue] property to
+ * allow storing offsets in arrays or collections of primitives without boxing.
+ *
+ * @param packedValue [Long] value encoding the [x] and [y] components of the [Offset]. Encoded
+ * values can be obtained by using the [packedValue] property of existing [Offset] instances.
*/
-@Suppress("NOTHING_TO_INLINE")
@Immutable
@kotlin.jvm.JvmInline
-value class Offset
-@PublishedApi
-internal constructor(@PublishedApi internal val packedValue: Long) {
+value class Offset(val packedValue: Long) {
@Stable
inline val x: Float
get() = unpackFloat1(packedValue)
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
index d47e8e4..e288fde 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@file:Suppress("NOTHING_TO_INLINE")
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package androidx.compose.ui.geometry
@@ -36,10 +36,22 @@
* Holds a 2D floating-point size.
*
* You can think of this as an [Offset] from the origin.
+ *
+ * To create a [Size], call the top-level function that accepts a width/height pair of dimensions:
+ * ```
+ * val size = Size(width, height)
+ * ```
+ *
+ * The primary constructor of [Size] is intended to be used with the [packedValue] property to allow
+ * storing sizes in arrays or collections of primitives without boxing.
+ *
+ * @param packedValue [Long] value encoding the [width] and [height] components of the [Size].
+ * Encoded values can be obtained by using the [packedValue] property of existing [Size]
+ * instances.
*/
@Immutable
@kotlin.jvm.JvmInline
-value class Size @PublishedApi internal constructor(@PublishedApi internal val packedValue: Long) {
+value class Size(val packedValue: Long) {
@Stable
inline val width: Float
get() = unpackFloat1(packedValue)
@@ -57,7 +69,6 @@
Size(packFloats(width, height))
companion object {
-
/** An empty size, one with a zero width and a zero height. */
@Stable val Zero = Size(0x0L)
diff --git a/compose/ui/ui-unit/api/current.txt b/compose/ui/ui-unit/api/current.txt
index 08d8d65..c372803 100644
--- a/compose/ui/ui-unit/api/current.txt
+++ b/compose/ui/ui-unit/api/current.txt
@@ -94,7 +94,7 @@
}
public final class DpKt {
- method @androidx.compose.runtime.Stable public static long DpOffset(float x, float y);
+ method @androidx.compose.runtime.Stable public static inline long DpOffset(float x, float y);
method @androidx.compose.runtime.Stable public static long DpSize(float width, float height);
method @androidx.compose.runtime.Stable public static inline float coerceAtLeast(float, float minimumValue);
method @androidx.compose.runtime.Stable public static inline float coerceAtMost(float, float maximumValue);
@@ -129,11 +129,14 @@
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class DpOffset {
+ ctor public DpOffset(long packedValue);
method public long copy(optional float x, optional float y);
+ method public long getPackedValue();
method public float getX();
method public float getY();
method @androidx.compose.runtime.Stable public operator long minus(long other);
method @androidx.compose.runtime.Stable public operator long plus(long other);
+ property public final long packedValue;
property @androidx.compose.runtime.Stable public final float x;
property @androidx.compose.runtime.Stable public final float y;
field public static final androidx.compose.ui.unit.DpOffset.Companion Companion;
@@ -203,10 +206,12 @@
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class IntOffset {
+ ctor public IntOffset(long packedValue);
method @androidx.compose.runtime.Stable public inline operator int component1();
method @androidx.compose.runtime.Stable public inline operator int component2();
method public long copy(optional int x, optional int y);
method @androidx.compose.runtime.Stable public operator long div(float operand);
+ method public long getPackedValue();
method public int getX();
method public int getY();
method @androidx.compose.runtime.Stable public operator long minus(long other);
@@ -214,6 +219,7 @@
method @androidx.compose.runtime.Stable public operator long rem(int operand);
method @androidx.compose.runtime.Stable public operator long times(float operand);
method @androidx.compose.runtime.Stable public operator long unaryMinus();
+ property public final long packedValue;
property @androidx.compose.runtime.Stable public final int x;
property @androidx.compose.runtime.Stable public final int y;
field public static final androidx.compose.ui.unit.IntOffset.Companion Companion;
@@ -225,7 +231,7 @@
}
public final class IntOffsetKt {
- method @androidx.compose.runtime.Stable public static long IntOffset(int x, int y);
+ method @androidx.compose.runtime.Stable public static inline long IntOffset(int x, int y);
method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
method @androidx.compose.runtime.Stable public static operator long minus(long, long offset);
method @androidx.compose.runtime.Stable public static operator long minus(long, long offset);
@@ -309,9 +315,11 @@
method @androidx.compose.runtime.Stable public inline operator int component2();
method @androidx.compose.runtime.Stable public operator long div(int other);
method public inline int getHeight();
+ method public long getPackedValue();
method public inline int getWidth();
method @androidx.compose.runtime.Stable public operator long times(int other);
property @androidx.compose.runtime.Stable public final inline int height;
+ property public final long packedValue;
property @androidx.compose.runtime.Stable public final inline int width;
field public static final androidx.compose.ui.unit.IntSize.Companion Companion;
}
diff --git a/compose/ui/ui-unit/api/restricted_current.txt b/compose/ui/ui-unit/api/restricted_current.txt
index 8732633..6f13e9f 100644
--- a/compose/ui/ui-unit/api/restricted_current.txt
+++ b/compose/ui/ui-unit/api/restricted_current.txt
@@ -94,7 +94,7 @@
}
public final class DpKt {
- method @androidx.compose.runtime.Stable public static long DpOffset(float x, float y);
+ method @androidx.compose.runtime.Stable public static inline long DpOffset(float x, float y);
method @androidx.compose.runtime.Stable public static long DpSize(float width, float height);
method @androidx.compose.runtime.Stable public static inline float coerceAtLeast(float, float minimumValue);
method @androidx.compose.runtime.Stable public static inline float coerceAtMost(float, float maximumValue);
@@ -129,11 +129,14 @@
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class DpOffset {
+ ctor public DpOffset(long packedValue);
method public long copy(optional float x, optional float y);
+ method public long getPackedValue();
method public float getX();
method public float getY();
method @androidx.compose.runtime.Stable public operator long minus(long other);
method @androidx.compose.runtime.Stable public operator long plus(long other);
+ property public final long packedValue;
property @androidx.compose.runtime.Stable public final float x;
property @androidx.compose.runtime.Stable public final float y;
field public static final androidx.compose.ui.unit.DpOffset.Companion Companion;
@@ -203,10 +206,12 @@
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class IntOffset {
+ ctor public IntOffset(long packedValue);
method @androidx.compose.runtime.Stable public inline operator int component1();
method @androidx.compose.runtime.Stable public inline operator int component2();
method public long copy(optional int x, optional int y);
method @androidx.compose.runtime.Stable public operator long div(float operand);
+ method public long getPackedValue();
method public int getX();
method public int getY();
method @androidx.compose.runtime.Stable public operator long minus(long other);
@@ -214,6 +219,7 @@
method @androidx.compose.runtime.Stable public operator long rem(int operand);
method @androidx.compose.runtime.Stable public operator long times(float operand);
method @androidx.compose.runtime.Stable public operator long unaryMinus();
+ property public final long packedValue;
property @androidx.compose.runtime.Stable public final int x;
property @androidx.compose.runtime.Stable public final int y;
field public static final androidx.compose.ui.unit.IntOffset.Companion Companion;
@@ -225,7 +231,7 @@
}
public final class IntOffsetKt {
- method @androidx.compose.runtime.Stable public static long IntOffset(int x, int y);
+ method @androidx.compose.runtime.Stable public static inline long IntOffset(int x, int y);
method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
method @androidx.compose.runtime.Stable public static operator long minus(long, long offset);
method @androidx.compose.runtime.Stable public static operator long minus(long, long offset);
@@ -305,14 +311,16 @@
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class IntSize {
- ctor @kotlin.PublishedApi internal IntSize(@kotlin.PublishedApi long packedValue);
+ ctor @kotlin.PublishedApi internal IntSize(long packedValue);
method @androidx.compose.runtime.Stable public inline operator int component1();
method @androidx.compose.runtime.Stable public inline operator int component2();
method @androidx.compose.runtime.Stable public operator long div(int other);
method public inline int getHeight();
+ method public long getPackedValue();
method public inline int getWidth();
method @androidx.compose.runtime.Stable public operator long times(int other);
property @androidx.compose.runtime.Stable public final inline int height;
+ property public final long packedValue;
property @androidx.compose.runtime.Stable public final inline int width;
field public static final androidx.compose.ui.unit.IntSize.Companion Companion;
}
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
index 4cc9af8..45a4b6d 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@file:Suppress("NOTHING_TO_INLINE")
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package androidx.compose.ui.unit
@@ -181,12 +181,25 @@
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/** Constructs a [DpOffset] from [x] and [y] position [Dp] values. */
-@Stable fun DpOffset(x: Dp, y: Dp): DpOffset = DpOffset(packFloats(x.value, y.value))
+@Stable inline fun DpOffset(x: Dp, y: Dp): DpOffset = DpOffset(packFloats(x.value, y.value))
-/** A two-dimensional offset using [Dp] for units */
+/**
+ * A two-dimensional offset using [Dp] for units.
+ *
+ * To create a [DpOffset], call the top-level function that accepts an x/y pair of coordinates:
+ * ```
+ * val offset = DpOffset(x, y)
+ * ```
+ *
+ * The primary constructor of [DpOffset] is intended to be used with the [packedValue] property to
+ * allow storing offsets in arrays or collections of primitives without boxing.
+ *
+ * @param packedValue [Long] value encoding the [x] and [y] components of the [DpOffset]. Encoded
+ * values can be obtained by using the [packedValue] property of existing [DpOffset] instances.
+ */
@Immutable
@JvmInline
-value class DpOffset internal constructor(@PublishedApi internal val packedValue: Long) {
+value class DpOffset(val packedValue: Long) {
/** The horizontal aspect of the offset in [Dp] */
@Stable
val x: Dp
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt
index 9bafe44..ca4caa3 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@file:Suppress("NOTHING_TO_INLINE")
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package androidx.compose.ui.unit
@@ -29,12 +29,25 @@
import kotlin.jvm.JvmInline
/** Constructs a [IntOffset] from [x] and [y] position [Int] values. */
-@Stable fun IntOffset(x: Int, y: Int): IntOffset = IntOffset(packInts(x, y))
+@Stable inline fun IntOffset(x: Int, y: Int): IntOffset = IntOffset(packInts(x, y))
-/** A two-dimensional position using [Int] pixels for units */
+/**
+ * A two-dimensional position using [Int] pixels for units.
+ *
+ * To create an [IntOffset], call the top-level function that accepts an x/y pair of coordinates:
+ * ```
+ * val offset = IntOffset(x, y)
+ * ```
+ *
+ * The primary constructor of [IntOffset] is intended to be used with the [packedValue] property to
+ * allow storing offsets in arrays or collections of primitives without boxing.
+ *
+ * @param packedValue [Long] value encoding the [x] and [y] components of the [IntOffset]. Encoded
+ * values can be obtained by using the [packedValue] property of existing [IntOffset] instances.
+ */
@Immutable
@JvmInline
-value class IntOffset internal constructor(@PublishedApi internal val packedValue: Long) {
+value class IntOffset(val packedValue: Long) {
/** The horizontal aspect of the position in [Int] pixels. */
@Stable
val x: Int
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntSize.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntSize.kt
index 48c1d7d..021b42d 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntSize.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntSize.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@file:Suppress("NOTHING_TO_INLINE")
+@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package androidx.compose.ui.unit
@@ -29,12 +29,25 @@
/** Constructs an [IntSize] from width and height [Int] values. */
@Stable inline fun IntSize(width: Int, height: Int): IntSize = IntSize(packInts(width, height))
-/** A two-dimensional size class used for measuring in [Int] pixels. */
+/**
+ * A two-dimensional size class used for measuring in [Int] pixels.
+ *
+ * To create an [IntSize], call the top-level function that accepts a width/height pair of
+ * dimensions:
+ * ```
+ * val size = IntSize(width, height)
+ * ```
+ *
+ * The primary constructor of [IntSize] is intended to be used with the [packedValue] property to
+ * allow storing sizes in arrays or collections of primitives without boxing.
+ *
+ * @param packedValue [Long] value encoding the [width] and [height] components of the [IntSize].
+ * Encoded values can be obtained by using the [packedValue] property of existing [IntSize]
+ * instances.
+ */
@Immutable
@kotlin.jvm.JvmInline
-value class IntSize
-@PublishedApi
-internal constructor(@PublishedApi internal val packedValue: Long) {
+value class IntSize @PublishedApi internal constructor(val packedValue: Long) {
/** The horizontal aspect of the size in [Int] pixels. */
@Stable
inline val width: Int
diff --git a/compose/ui/ui-util/api/current.txt b/compose/ui/ui-util/api/current.txt
index 216790ac..57823b5 100644
--- a/compose/ui/ui-util/api/current.txt
+++ b/compose/ui/ui-util/api/current.txt
@@ -66,10 +66,16 @@
method public static float fastCbrt(float x);
method public static inline double fastCoerceAtLeast(double, double minimumValue);
method public static inline float fastCoerceAtLeast(float, float minimumValue);
+ method public static inline int fastCoerceAtLeast(int, int minimumValue);
+ method public static inline long fastCoerceAtLeast(long, long minimumValue);
method public static inline double fastCoerceAtMost(double, double maximumValue);
method public static inline float fastCoerceAtMost(float, float maximumValue);
+ method public static inline int fastCoerceAtMost(int, int maximumValue);
+ method public static inline long fastCoerceAtMost(long, long maximumValue);
method public static inline double fastCoerceIn(double, double minimumValue, double maximumValue);
method public static inline float fastCoerceIn(float, float minimumValue, float maximumValue);
+ method public static inline int fastCoerceIn(int, int minimumValue, int maximumValue);
+ method public static inline long fastCoerceIn(long, long minimumValue, long maximumValue);
method public static inline boolean fastIsFinite(double);
method public static inline boolean fastIsFinite(float);
method public static inline float fastMaxOf(float a, float b, float c, float d);
diff --git a/compose/ui/ui-util/api/restricted_current.txt b/compose/ui/ui-util/api/restricted_current.txt
index 216790ac..57823b5 100644
--- a/compose/ui/ui-util/api/restricted_current.txt
+++ b/compose/ui/ui-util/api/restricted_current.txt
@@ -66,10 +66,16 @@
method public static float fastCbrt(float x);
method public static inline double fastCoerceAtLeast(double, double minimumValue);
method public static inline float fastCoerceAtLeast(float, float minimumValue);
+ method public static inline int fastCoerceAtLeast(int, int minimumValue);
+ method public static inline long fastCoerceAtLeast(long, long minimumValue);
method public static inline double fastCoerceAtMost(double, double maximumValue);
method public static inline float fastCoerceAtMost(float, float maximumValue);
+ method public static inline int fastCoerceAtMost(int, int maximumValue);
+ method public static inline long fastCoerceAtMost(long, long maximumValue);
method public static inline double fastCoerceIn(double, double minimumValue, double maximumValue);
method public static inline float fastCoerceIn(float, float minimumValue, float maximumValue);
+ method public static inline int fastCoerceIn(int, int minimumValue, int maximumValue);
+ method public static inline long fastCoerceIn(long, long minimumValue, long maximumValue);
method public static inline boolean fastIsFinite(double);
method public static inline boolean fastIsFinite(float);
method public static inline float fastMaxOf(float a, float b, float c, float d);
diff --git a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt
index fa82086..ace3e1b 100644
--- a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt
+++ b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt
@@ -93,6 +93,42 @@
}
/**
+ * Returns this integer value clamped in the inclusive range defined by [minimumValue] and
+ * [maximumValue]. Unlike [Int.coerceIn], the range is not validated: the caller must ensure that
+ * [minimumValue] is less than [maximumValue].
+ */
+inline fun Int.fastCoerceIn(minimumValue: Int, maximumValue: Int) =
+ this.fastCoerceAtLeast(minimumValue).fastCoerceAtMost(maximumValue)
+
+/** Ensures that this value is not less than the specified [minimumValue]. */
+inline fun Int.fastCoerceAtLeast(minimumValue: Int): Int {
+ return if (this < minimumValue) minimumValue else this
+}
+
+/** Ensures that this value is not greater than the specified [maximumValue]. */
+inline fun Int.fastCoerceAtMost(maximumValue: Int): Int {
+ return if (this > maximumValue) maximumValue else this
+}
+
+/**
+ * Returns this long value clamped in the inclusive range defined by [minimumValue] and
+ * [maximumValue]. Unlike [Long.coerceIn], the range is not validated: the caller must ensure that
+ * [minimumValue] is less than [maximumValue].
+ */
+inline fun Long.fastCoerceIn(minimumValue: Long, maximumValue: Long) =
+ this.fastCoerceAtLeast(minimumValue).fastCoerceAtMost(maximumValue)
+
+/** Ensures that this value is not less than the specified [minimumValue]. */
+inline fun Long.fastCoerceAtLeast(minimumValue: Long): Long {
+ return if (this < minimumValue) minimumValue else this
+}
+
+/** Ensures that this value is not greater than the specified [maximumValue]. */
+inline fun Long.fastCoerceAtMost(maximumValue: Long): Long {
+ return if (this > maximumValue) maximumValue else this
+}
+
+/**
* Returns `true` if this float is a finite floating-point value; returns `false` otherwise (for
* `NaN` and infinity).
*/
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/NestedScrollingBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/NestedScrollingBenchmark.kt
index 9c909f6..dfd58d7 100644
--- a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/NestedScrollingBenchmark.kt
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/NestedScrollingBenchmark.kt
@@ -34,6 +34,9 @@
import androidx.test.filters.LargeTest
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,6 +48,7 @@
private val nestedScrollingCaseFactory = { NestedScrollingTestCase() }
+ @Ignore("b/362302352")
@Test
fun nested_scroll_propagation() {
benchmarkRule.runBenchmarkFor(nestedScrollingCaseFactory) {
@@ -136,12 +140,12 @@
}
fun assertPostToggle() {
- assert(collectedDeltasOuter != Offset.Zero)
- assert(collectedDeltasMiddle != Offset.Zero)
- assert(collectedVelocityOuter != Velocity.Zero)
- assert(collectedVelocityMiddle != Velocity.Zero)
+ assertNotEquals(collectedDeltasOuter, Offset.Zero)
+ assertNotEquals(collectedDeltasMiddle, Offset.Zero)
+ assertNotEquals(collectedVelocityOuter, Velocity.Zero)
+ assertNotEquals(collectedVelocityMiddle, Velocity.Zero)
- assert(collectedDeltasOuter == scrollResult)
- assert(collectedVelocityOuter == velocityResult)
+ assertEquals(scrollResult, collectedDeltasOuter)
+ assertEquals(velocityResult, collectedVelocityOuter)
}
}
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/input/pointer/VelocityTrackerBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/input/pointer/VelocityTrackerBenchmark.kt
index d78ffcb..a0e4339 100644
--- a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/input/pointer/VelocityTrackerBenchmark.kt
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/input/pointer/VelocityTrackerBenchmark.kt
@@ -19,6 +19,7 @@
import androidx.compose.ui.input.pointer.util.VelocityTracker1D
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -95,7 +96,7 @@
velocityTracker.addDataPoint(dataPoint.timeMillis, dataPoint.motionValue)
}
- benchmarkRule.measureRepeated { assert(velocityTracker.calculateVelocity() != 0f) }
+ benchmarkRule.measureRepeated { assertTrue(velocityTracker.calculateVelocity() != 0f) }
}
companion object {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
index 223da87..38f3d5c 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
@@ -2557,8 +2557,9 @@
// Verify initial offset, should be the same values for the "excluded" offset
positionToExcludedArray.forEachIndexed { index, (position, excluded) ->
// Rounding to avoid -0.0f
- assertEquals((index * boxSizePx).fastRoundToInt(), position.y.fastRoundToInt())
- assertEquals((index * boxSizePx).fastRoundToInt(), excluded.y.fastRoundToInt())
+ val expected = (index * boxSizePx).fastRoundToInt()
+ assertEquals("At index: $index", expected, position.y.fastRoundToInt())
+ assertEquals("At index: $index", expected, excluded.y.fastRoundToInt())
}
// Scroll to the end
@@ -2633,8 +2634,9 @@
// Verify initial offset, should be the same values for the "excluded" offset
positionToExcludedArray.forEachIndexed { index, (position, excluded) ->
// Rounding to avoid -0.0f
- assertEquals((index * boxSizePx).fastRoundToInt(), position.y.fastRoundToInt())
- assertEquals((index * boxSizePx).fastRoundToInt(), excluded.y.fastRoundToInt())
+ val expected = (index * boxSizePx).fastRoundToInt()
+ assertEquals("At index: $index", expected, position.y.fastRoundToInt())
+ assertEquals("At index: $index", expected, excluded.y.fastRoundToInt())
}
// Scroll to the end
@@ -3102,6 +3104,134 @@
assertEquals(0, lookingAheadPositionExcludingDmp.y.fastRoundToInt())
}
+ @Test
+ fun testLookaheadAndApproachCoordinatesAreConsistentOnFirstPass_usingAlign() =
+ with(rule.density) {
+ val rootSizePx = 300
+ val alignmentOffsetPx = IntOffset(0, 100)
+
+ // Both positions are expected to be from lookahead coordinates
+ var lookaheadPassPosition = Offset.Unspecified
+ var approachPassPosition = Offset.Unspecified
+
+ rule.setContent {
+ Box(Modifier.size(rootSizePx.toDp())) {
+ LookaheadScope {
+ Box(Modifier.align { _, _, _ -> alignmentOffsetPx }.fillMaxWidth()) {
+ // Capture lookahead coordinates from Lookahead and Approach pass.
+ Box(
+ Modifier.onLookaheadPassCoordinates(this@LookaheadScope) {
+ lookaheadScopeCoordinates,
+ coordinates ->
+ lookaheadPassPosition =
+ lookaheadScopeCoordinates.localPositionOf(coordinates)
+ }
+ .onApproachPassCoordinates(this@LookaheadScope) {
+ lookaheadScopeCoordinates,
+ coordinates ->
+ approachPassPosition =
+ lookaheadScopeCoordinates.localLookaheadPositionOf(
+ coordinates
+ )
+ }
+ )
+ }
+ }
+ }
+ }
+ rule.waitForIdle()
+
+ // Assert both positions are equal on the first pass.
+ assertEquals(alignmentOffsetPx, lookaheadPassPosition.round())
+ assertEquals(alignmentOffsetPx, approachPassPosition.round())
+ }
+
+ @Test
+ fun testLookaheadAndApproachCoordinatesAreConsistentOnFirstPass_usingOffset() =
+ with(rule.density) {
+ val rootSizePx = 300
+ val alignmentOffsetPx = IntOffset(0, 100)
+
+ // Both positions are expected to be from lookahead coordinates
+ var lookaheadPassPosition = Offset.Unspecified
+ var approachPassPosition = Offset.Unspecified
+
+ rule.setContent {
+ Box(Modifier.size(rootSizePx.toDp())) {
+ LookaheadScope {
+ Box(Modifier.offset { alignmentOffsetPx }.fillMaxWidth()) {
+ // Capture lookahead coordinates from Lookahead and Approach pass.
+ Box(
+ Modifier.onLookaheadPassCoordinates(this@LookaheadScope) {
+ lookaheadScopeCoordinates,
+ coordinates ->
+ lookaheadPassPosition =
+ lookaheadScopeCoordinates.localPositionOf(coordinates)
+ }
+ .onApproachPassCoordinates(this@LookaheadScope) {
+ lookaheadScopeCoordinates,
+ coordinates ->
+ approachPassPosition =
+ lookaheadScopeCoordinates.localLookaheadPositionOf(
+ coordinates
+ )
+ }
+ )
+ }
+ }
+ }
+ }
+ rule.waitForIdle()
+
+ // Assert both positions are equal on the first pass.
+ assertEquals(alignmentOffsetPx, lookaheadPassPosition.round())
+ assertEquals(alignmentOffsetPx, approachPassPosition.round())
+ }
+
+ /** Capture LookaheadScope coordinates during the Lookahead pass. */
+ private fun Modifier.onLookaheadPassCoordinates(
+ lookaheadScope: LookaheadScope,
+ onLookaheadPassCoordinates:
+ (
+ lookaheadScopeCoordinates: LayoutCoordinates, layoutCoordinates: LayoutCoordinates
+ ) -> Unit
+ ): Modifier =
+ with(lookaheadScope) {
+ [email protected] { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) {
+ if (isLookingAhead) {
+ coordinates?.let { coordinates ->
+ onLookaheadPassCoordinates(lookaheadScopeCoordinates, coordinates)
+ }
+ }
+ placeable.place(0, 0)
+ }
+ }
+ }
+
+ /** Capture LookaheadScope coordinates during the Approach pass. */
+ private fun Modifier.onApproachPassCoordinates(
+ lookaheadScope: LookaheadScope,
+ onApproachPassCoordinates:
+ (
+ lookaheadScopeCoordinates: LayoutCoordinates, layoutCoordinates: LayoutCoordinates
+ ) -> Unit
+ ): Modifier =
+ with(lookaheadScope) {
+ [email protected](
+ isMeasurementApproachInProgress = { false },
+ isPlacementApproachInProgress = {
+ onApproachPassCoordinates(lookaheadScopeCoordinates, it)
+ false
+ },
+ approachMeasure = { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) { placeable.place(0, 0) }
+ }
+ )
+ }
+
private fun assertSameLayoutWithAndWithoutLookahead(
content: @Composable (modifier: Modifier) -> Unit
) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index 6fa2b45..72202ff 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -268,7 +268,7 @@
// flaky, so we use this callback to test accessibility events.
@VisibleForTesting
internal var onSendAccessibilityEvent: (AccessibilityEvent) -> Boolean = {
- trace("sendAccessibilityEvent") { view.parent.requestSendAccessibilityEvent(view, it) }
+ view.parent.requestSendAccessibilityEvent(view, it)
}
private val accessibilityManager: AccessibilityManager =
@@ -367,12 +367,9 @@
get() {
if (currentSemanticsNodesInvalidated) { // first instance of retrieving all nodes
currentSemanticsNodesInvalidated = false
- field =
- trace("generateCurrentSemanticsNodes") {
- view.semanticsOwner.getAllUncoveredSemanticsNodesToIntObjectMap()
- }
+ field = view.semanticsOwner.getAllUncoveredSemanticsNodesToIntObjectMap()
if (isEnabled) {
- trace("setTraversalValues") { setTraversalValues() }
+ setTraversalValues()
}
}
return field
@@ -497,43 +494,32 @@
}
private fun createNodeInfo(virtualViewId: Int): AccessibilityNodeInfoCompat? {
- trace("checkIfDestroyed") {
- if (
- view.viewTreeOwners?.lifecycleOwner?.lifecycle?.currentState ==
- Lifecycle.State.DESTROYED
- ) {
- return null
- }
+ if (
+ view.viewTreeOwners?.lifecycleOwner?.lifecycle?.currentState ==
+ Lifecycle.State.DESTROYED
+ ) {
+ return null
}
- val info: AccessibilityNodeInfoCompat =
- trace("createAccessibilityNodeInfoObject") { AccessibilityNodeInfoCompat.obtain() }
- val semanticsNodeWithAdjustedBounds =
- trace("calculateNodeWithAdjustedBounds") { currentSemanticsNodes[virtualViewId] }
- ?: return null
+ val info: AccessibilityNodeInfoCompat = AccessibilityNodeInfoCompat.obtain()
+ val semanticsNodeWithAdjustedBounds = currentSemanticsNodes[virtualViewId] ?: return null
val semanticsNode: SemanticsNode = semanticsNodeWithAdjustedBounds.semanticsNode
- trace("setParentForAccessibility") {
- if (virtualViewId == AccessibilityNodeProviderCompat.HOST_VIEW_ID) {
- info.setParent(view.getParentForAccessibility() as? View)
- } else {
- var parentId =
- checkPreconditionNotNull(semanticsNode.parent?.id) {
- "semanticsNode $virtualViewId has null parent"
- }
- if (parentId == view.semanticsOwner.unmergedRootSemanticsNode.id) {
- parentId = AccessibilityNodeProviderCompat.HOST_VIEW_ID
+ if (virtualViewId == AccessibilityNodeProviderCompat.HOST_VIEW_ID) {
+ info.setParent(view.getParentForAccessibility() as? View)
+ } else {
+ var parentId =
+ checkPreconditionNotNull(semanticsNode.parent?.id) {
+ "semanticsNode $virtualViewId has null parent"
}
- info.setParent(view, parentId)
+ if (parentId == view.semanticsOwner.unmergedRootSemanticsNode.id) {
+ parentId = AccessibilityNodeProviderCompat.HOST_VIEW_ID
}
+ info.setParent(view, parentId)
}
info.setSource(view, virtualViewId)
- trace("setBoundsInScreen") {
- info.setBoundsInScreen(boundsInScreen(semanticsNodeWithAdjustedBounds))
- }
+ info.setBoundsInScreen(boundsInScreen(semanticsNodeWithAdjustedBounds))
- trace("populateAccessibilityNodeInfoProperties") {
- populateAccessibilityNodeInfoProperties(virtualViewId, info, semanticsNode)
- }
+ populateAccessibilityNodeInfoProperties(virtualViewId, info, semanticsNode)
return info
}
@@ -1570,14 +1556,13 @@
@Suppress("DEPRECATION")
@VisibleForTesting
private fun createEvent(virtualViewId: Int, eventType: Int): AccessibilityEvent {
- val event: AccessibilityEvent =
- trace("obtainAccessibilityEvent") { AccessibilityEvent.obtain(eventType) }
+ val event: AccessibilityEvent = AccessibilityEvent.obtain(eventType)
event.isEnabled = true
event.className = ClassName
// Don't allow the client to override these properties.
- trace("event.packageName") { event.packageName = view.context.packageName }
- trace("event.setSource") { event.setSource(view, virtualViewId) }
+ event.packageName = view.context.packageName
+ event.setSource(view, virtualViewId)
if (isEnabled) {
// populate additional information from the node
@@ -2261,15 +2246,11 @@
if (isEnabled) {
for (i in subtreeChangedLayoutNodes.indices) {
val layoutNode = subtreeChangedLayoutNodes.valueAt(i)
- trace("sendSubtreeChangeAccessibilityEvents") {
- sendSubtreeChangeAccessibilityEvents(
- layoutNode,
- subtreeChangedSemanticsNodesIds
- )
- }
- trace("sendTypeViewScrolledAccessibilityEvent") {
- sendTypeViewScrolledAccessibilityEvent(layoutNode)
- }
+ sendSubtreeChangeAccessibilityEvents(
+ layoutNode,
+ subtreeChangedSemanticsNodesIds
+ )
+ sendTypeViewScrolledAccessibilityEvent(layoutNode)
}
subtreeChangedSemanticsNodesIds.clear()
// When the bounds of layout nodes change, we will not always get semantics
@@ -2369,22 +2350,19 @@
}
// When we finally send the event, make sure it is an accessibility-focusable node.
- val id =
- trace("GetSemanticsNode") {
- var semanticsNode =
- if (layoutNode.nodes.has(Nodes.Semantics)) layoutNode
- else layoutNode.findClosestParentNode { it.nodes.has(Nodes.Semantics) }
+ var semanticsNode =
+ if (layoutNode.nodes.has(Nodes.Semantics)) layoutNode
+ else layoutNode.findClosestParentNode { it.nodes.has(Nodes.Semantics) }
- val config = semanticsNode?.collapsedSemantics ?: return
- if (!config.isMergingSemanticsOfDescendants) {
- semanticsNode
- .findClosestParentNode {
- it.collapsedSemantics?.isMergingSemanticsOfDescendants == true
- }
- ?.let { semanticsNode = it }
+ val config = semanticsNode?.collapsedSemantics ?: return
+ if (!config.isMergingSemanticsOfDescendants) {
+ semanticsNode
+ .findClosestParentNode {
+ it.collapsedSemantics?.isMergingSemanticsOfDescendants == true
}
- semanticsNode?.semanticsId ?: return
- }
+ ?.let { semanticsNode = it }
+ }
+ val id = semanticsNode?.semanticsId ?: return
if (!subtreeChangedSemanticsNodesIds.add(id)) {
return
@@ -3175,11 +3153,9 @@
private inner class ComposeAccessibilityNodeProvider : AccessibilityNodeProviderCompat() {
override fun createAccessibilityNodeInfo(virtualViewId: Int): AccessibilityNodeInfoCompat? {
- return trace("createAccessibilityNodeInfo") {
- createNodeInfo(virtualViewId).also {
- if (sendingFocusAffectingEvent && virtualViewId == focusedVirtualViewId) {
- currentlyFocusedANI = it
- }
+ return createNodeInfo(virtualViewId).also {
+ if (sendingFocusAffectingEvent && virtualViewId == focusedVirtualViewId) {
+ currentlyFocusedANI = it
}
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadLayoutCoordinates.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadLayoutCoordinates.kt
index 9c1faaf..52695a6 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadLayoutCoordinates.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadLayoutCoordinates.kt
@@ -141,20 +141,26 @@
)
}
} else {
- val rootDelegate = lookaheadDelegate.rootLookaheadDelegate
// This is a case of mixed coordinates where `this` is lookahead coords, and
// `sourceCoordinates` isn't. Therefore we'll break this into two parts:
// local position in lookahead coords space && local position in regular layout coords
// space.
+ val rootDelegate = lookaheadDelegate.rootLookaheadDelegate
+
val localLookaheadPos =
localPositionOf(
sourceCoordinates = rootDelegate.lookaheadLayoutCoordinates,
relativeToSource = relativeToSource,
includeMotionFrameOfReference = includeMotionFrameOfReference
- )
+ ) - rootDelegate.position.toOffset()
+
+ // If Lookahead is the hierarchy's absolute root (no parent), we may use its coordinates
+ // directly
+ val rootDelegateCoordinates =
+ rootDelegate.coordinator.parentCoordinates ?: rootDelegate.coordinator.coordinates
val localPos =
- rootDelegate.coordinator.coordinates.localPositionOf(
+ rootDelegateCoordinates.localPositionOf(
sourceCoordinates = sourceCoordinates,
relativeToSource = Offset.Zero,
includeMotionFrameOfReference = includeMotionFrameOfReference
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 2403769..92695e5 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
@@ -60,7 +60,6 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.util.trace
import androidx.compose.ui.viewinterop.InteropView
import androidx.compose.ui.viewinterop.InteropViewFactoryHolder
@@ -409,27 +408,25 @@
// whether or not deactivated nodes should be considered removed or not.
if (!isAttached || isDeactivated) return null
- trace("collapseSemantics") {
- if (!nodes.has(Nodes.Semantics) || _collapsedSemantics != null) {
- return _collapsedSemantics
- }
-
- var config = SemanticsConfiguration()
- requireOwner().snapshotObserver.observeSemanticsReads(this) {
- nodes.tailToHead(Nodes.Semantics) {
- if (it.shouldClearDescendantSemantics) {
- config = SemanticsConfiguration()
- config.isClearingSemantics = true
- }
- if (it.shouldMergeDescendantSemantics) {
- config.isMergingSemanticsOfDescendants = true
- }
- with(config) { with(it) { applySemantics() } }
- }
- }
- _collapsedSemantics = config
- return config
+ if (!nodes.has(Nodes.Semantics) || _collapsedSemantics != null) {
+ return _collapsedSemantics
}
+
+ var config = SemanticsConfiguration()
+ requireOwner().snapshotObserver.observeSemanticsReads(this) {
+ nodes.tailToHead(Nodes.Semantics) {
+ if (it.shouldClearDescendantSemantics) {
+ config = SemanticsConfiguration()
+ config.isClearingSemantics = true
+ }
+ if (it.shouldMergeDescendantSemantics) {
+ config.isMergingSemanticsOfDescendants = true
+ }
+ with(config) { with(it) { applySemantics() } }
+ }
+ }
+ _collapsedSemantics = config
+ return config
}
/**
diff --git a/constraintlayout/constraintlayout-compose/api/1.1.0-beta01.txt b/constraintlayout/constraintlayout-compose/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..f9829bc
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose/api/1.1.0-beta01.txt
@@ -0,0 +1,1013 @@
+// Signature format: 4.0
+package androidx.constraintlayout.compose {
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class Arc {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.Arc.Companion Companion;
+ }
+
+ public static final class Arc.Companion {
+ method public androidx.constraintlayout.compose.Arc getAbove();
+ method public androidx.constraintlayout.compose.Arc getBelow();
+ method public androidx.constraintlayout.compose.Arc getFlip();
+ method public androidx.constraintlayout.compose.Arc getNone();
+ method public androidx.constraintlayout.compose.Arc getStartHorizontal();
+ method public androidx.constraintlayout.compose.Arc getStartVertical();
+ property public final androidx.constraintlayout.compose.Arc Above;
+ property public final androidx.constraintlayout.compose.Arc Below;
+ property public final androidx.constraintlayout.compose.Arc Flip;
+ property public final androidx.constraintlayout.compose.Arc None;
+ property public final androidx.constraintlayout.compose.Arc StartHorizontal;
+ property public final androidx.constraintlayout.compose.Arc StartVertical;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public abstract sealed class BaseKeyFrameScope {
+ method protected final <E extends androidx.constraintlayout.compose.NamedPropertyOrValue> kotlin.properties.ObservableProperty<E> addNameOnPropertyChange(E initialValue, optional String? nameOverride);
+ method protected final <T> kotlin.properties.ObservableProperty<T> addOnPropertyChange(T initialValue, optional String? nameOverride);
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public abstract sealed class BaseKeyFramesScope {
+ method public final androidx.constraintlayout.compose.Easing getEasing();
+ method public final void setEasing(androidx.constraintlayout.compose.Easing);
+ property public final androidx.constraintlayout.compose.Easing easing;
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface BaselineAnchorable {
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor anchor, optional float margin, optional float goneMargin);
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor, optional float margin, optional float goneMargin);
+ }
+
+ @androidx.compose.runtime.Immutable public final class ChainStyle {
+ field public static final androidx.constraintlayout.compose.ChainStyle.Companion Companion;
+ }
+
+ public static final class ChainStyle.Companion {
+ method @androidx.compose.runtime.Stable public androidx.constraintlayout.compose.ChainStyle Packed(float bias);
+ method public androidx.constraintlayout.compose.ChainStyle getPacked();
+ method public androidx.constraintlayout.compose.ChainStyle getSpread();
+ method public androidx.constraintlayout.compose.ChainStyle getSpreadInside();
+ property public final androidx.constraintlayout.compose.ChainStyle Packed;
+ property public final androidx.constraintlayout.compose.ChainStyle Spread;
+ property public final androidx.constraintlayout.compose.ChainStyle SpreadInside;
+ }
+
+ @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Stable public final class ConstrainScope {
+ method public androidx.constraintlayout.compose.Dimension asDimension(float);
+ method public void centerAround(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor);
+ method public void centerAround(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor anchor);
+ method public void centerHorizontallyTo(androidx.constraintlayout.compose.ConstrainedLayoutReference other, optional @FloatRange(from=0.0, to=1.0) float bias);
+ method public void centerTo(androidx.constraintlayout.compose.ConstrainedLayoutReference other);
+ method public void centerVerticallyTo(androidx.constraintlayout.compose.ConstrainedLayoutReference other, optional @FloatRange(from=0.0, to=1.0) float bias);
+ method public void circular(androidx.constraintlayout.compose.ConstrainedLayoutReference other, float angle, float distance);
+ method public void clearConstraints();
+ method public void clearHorizontal();
+ method public void clearVertical();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteLeft();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteRight();
+ method public float getAlpha();
+ method public androidx.constraintlayout.compose.BaselineAnchorable getBaseline();
+ method public androidx.constraintlayout.compose.HorizontalAnchorable getBottom();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getEnd();
+ method public androidx.constraintlayout.compose.Dimension getHeight();
+ method public float getHorizontalBias();
+ method public float getHorizontalChainWeight();
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference getParent();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getRotationZ();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getStart();
+ method public androidx.constraintlayout.compose.HorizontalAnchorable getTop();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public float getVerticalBias();
+ method public float getVerticalChainWeight();
+ method public androidx.constraintlayout.compose.Visibility getVisibility();
+ method public androidx.constraintlayout.compose.Dimension getWidth();
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor top, androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor bottom, optional float topMargin, optional float bottomMargin, optional float topGoneMargin, optional float bottomGoneMargin, optional @FloatRange(from=0.0, to=1.0) float bias);
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor start, androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor top, androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor end, androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor bottom, optional float startMargin, optional float topMargin, optional float endMargin, optional float bottomMargin, optional float startGoneMargin, optional float topGoneMargin, optional float endGoneMargin, optional float bottomGoneMargin, optional @FloatRange(from=0.0, to=1.0) float horizontalBias, optional @FloatRange(from=0.0, to=1.0) float verticalBias);
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor start, androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor end, optional float startMargin, optional float endMargin, optional float startGoneMargin, optional float endGoneMargin, optional @FloatRange(from=0.0, to=1.0) float bias);
+ method public void resetDimensions();
+ method public void resetTransforms();
+ method public void setAlpha(float);
+ method public void setHeight(androidx.constraintlayout.compose.Dimension);
+ method public void setHorizontalBias(float);
+ method public void setHorizontalChainWeight(float);
+ method public void setPivotX(float);
+ method public void setPivotY(float);
+ method public void setRotationX(float);
+ method public void setRotationY(float);
+ method public void setRotationZ(float);
+ method public void setScaleX(float);
+ method public void setScaleY(float);
+ method public void setTranslationX(float);
+ method public void setTranslationY(float);
+ method public void setTranslationZ(float);
+ method public void setVerticalBias(float);
+ method public void setVerticalChainWeight(float);
+ method public void setVisibility(androidx.constraintlayout.compose.Visibility);
+ method public void setWidth(androidx.constraintlayout.compose.Dimension);
+ property public final androidx.constraintlayout.compose.VerticalAnchorable absoluteLeft;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable absoluteRight;
+ property public final float alpha;
+ property public final androidx.constraintlayout.compose.BaselineAnchorable baseline;
+ property public final androidx.constraintlayout.compose.HorizontalAnchorable bottom;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable end;
+ property public final androidx.constraintlayout.compose.Dimension height;
+ property public final float horizontalBias;
+ property public final float horizontalChainWeight;
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference parent;
+ property public final float pivotX;
+ property public final float pivotY;
+ property public final float rotationX;
+ property public final float rotationY;
+ property public final float rotationZ;
+ property public final float scaleX;
+ property public final float scaleY;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable start;
+ property public final androidx.constraintlayout.compose.HorizontalAnchorable top;
+ property public final float translationX;
+ property public final float translationY;
+ property public final float translationZ;
+ property public final float verticalBias;
+ property public final float verticalChainWeight;
+ property public final androidx.constraintlayout.compose.Visibility visibility;
+ property public final androidx.constraintlayout.compose.Dimension width;
+ }
+
+ @androidx.compose.runtime.Stable public final class ConstrainedLayoutReference extends androidx.constraintlayout.compose.LayoutReference {
+ ctor public ConstrainedLayoutReference(Object id);
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getAbsoluteLeft();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getAbsoluteRight();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor getBaseline();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor getBottom();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getEnd();
+ method public Object getId();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getStart();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor getTop();
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor absoluteLeft;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor absoluteRight;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor baseline;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor bottom;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor end;
+ property public Object id;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor start;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor top;
+ }
+
+ public abstract class ConstraintLayoutBaseScope {
+ ctor public ConstraintLayoutBaseScope();
+ method public final void applyTo(androidx.constraintlayout.compose.State state);
+ method public final androidx.constraintlayout.compose.ConstrainScope constrain(androidx.constraintlayout.compose.ConstrainedLayoutReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
+ method public final void constrain(androidx.constraintlayout.compose.ConstrainedLayoutReference[] refs, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
+ method public final androidx.constraintlayout.compose.HorizontalChainScope constrain(androidx.constraintlayout.compose.HorizontalChainReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.HorizontalChainScope,kotlin.Unit> constrainBlock);
+ method public final androidx.constraintlayout.compose.VerticalChainScope constrain(androidx.constraintlayout.compose.VerticalChainReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.VerticalChainScope,kotlin.Unit> constrainBlock);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createAbsoluteLeftBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createAbsoluteRightBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createBottomBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createColumn(androidx.constraintlayout.compose.LayoutReference[] elements, optional float spacing, optional float[] weights);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createEndBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createFlow(androidx.constraintlayout.compose.LayoutReference?[] elements, optional boolean flowVertically, optional float verticalGap, optional float horizontalGap, optional int maxElement, optional float padding, optional androidx.constraintlayout.compose.Wrap wrapMode, optional androidx.constraintlayout.compose.VerticalAlign verticalAlign, optional androidx.constraintlayout.compose.HorizontalAlign horizontalAlign, optional float horizontalFlowBias, optional float verticalFlowBias, optional androidx.constraintlayout.compose.FlowStyle verticalStyle, optional androidx.constraintlayout.compose.FlowStyle horizontalStyle);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createFlow(androidx.constraintlayout.compose.LayoutReference?[] elements, optional boolean flowVertically, optional float verticalGap, optional float horizontalGap, optional int maxElement, optional float paddingHorizontal, optional float paddingVertical, optional androidx.constraintlayout.compose.Wrap wrapMode, optional androidx.constraintlayout.compose.VerticalAlign verticalAlign, optional androidx.constraintlayout.compose.HorizontalAlign horizontalAlign, optional float horizontalFlowBias, optional float verticalFlowBias, optional androidx.constraintlayout.compose.FlowStyle verticalStyle, optional androidx.constraintlayout.compose.FlowStyle horizontalStyle);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createFlow(androidx.constraintlayout.compose.LayoutReference?[] elements, optional boolean flowVertically, optional float verticalGap, optional float horizontalGap, optional int maxElement, optional float paddingLeft, optional float paddingTop, optional float paddingRight, optional float paddingBottom, optional androidx.constraintlayout.compose.Wrap wrapMode, optional androidx.constraintlayout.compose.VerticalAlign verticalAlign, optional androidx.constraintlayout.compose.HorizontalAlign horizontalAlign, optional float horizontalFlowBias, optional float verticalFlowBias, optional androidx.constraintlayout.compose.FlowStyle verticalStyle, optional androidx.constraintlayout.compose.FlowStyle horizontalStyle);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createGrid(androidx.constraintlayout.compose.LayoutReference[] elements, @IntRange(from=1L) int rows, @IntRange(from=1L) int columns, optional boolean isHorizontalArrangement, optional float verticalSpacing, optional float horizontalSpacing, optional float[] rowWeights, optional float[] columnWeights, optional androidx.constraintlayout.compose.Skip[] skips, optional androidx.constraintlayout.compose.Span[] spans, optional int flags);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromAbsoluteLeft(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromAbsoluteLeft(float fraction);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromAbsoluteRight(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromAbsoluteRight(float fraction);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createGuidelineFromBottom(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createGuidelineFromBottom(float fraction);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromEnd(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromEnd(float fraction);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromStart(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromStart(float fraction);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createGuidelineFromTop(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createGuidelineFromTop(float fraction);
+ method public final androidx.constraintlayout.compose.HorizontalChainReference createHorizontalChain(androidx.constraintlayout.compose.LayoutReference[] elements, optional androidx.constraintlayout.compose.ChainStyle chainStyle);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createRow(androidx.constraintlayout.compose.LayoutReference[] elements, optional float spacing, optional float[] weights);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createStartBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createTopBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.VerticalChainReference createVerticalChain(androidx.constraintlayout.compose.LayoutReference[] elements, optional androidx.constraintlayout.compose.ChainStyle chainStyle);
+ method @Deprecated protected final java.util.List<kotlin.jvm.functions.Function1<androidx.constraintlayout.compose.State,kotlin.Unit>> getTasks();
+ method public void reset();
+ method public final androidx.constraintlayout.compose.LayoutReference withChainParams(androidx.constraintlayout.compose.LayoutReference, optional float startMargin, optional float topMargin, optional float endMargin, optional float bottomMargin, optional float startGoneMargin, optional float topGoneMargin, optional float endGoneMargin, optional float bottomGoneMargin, optional float weight);
+ method public final androidx.constraintlayout.compose.LayoutReference withHorizontalChainParams(androidx.constraintlayout.compose.LayoutReference, optional float startMargin, optional float endMargin, optional float startGoneMargin, optional float endGoneMargin, optional float weight);
+ method public final androidx.constraintlayout.compose.LayoutReference withVerticalChainParams(androidx.constraintlayout.compose.LayoutReference, optional float topMargin, optional float bottomMargin, optional float topGoneMargin, optional float bottomGoneMargin, optional float weight);
+ property @Deprecated protected final java.util.List<kotlin.jvm.functions.Function1<androidx.constraintlayout.compose.State,kotlin.Unit>> tasks;
+ }
+
+ @androidx.compose.runtime.Stable public static final class ConstraintLayoutBaseScope.BaselineAnchor {
+ method public androidx.constraintlayout.compose.LayoutReference component2();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor copy(Object id, androidx.constraintlayout.compose.LayoutReference reference);
+ method public androidx.constraintlayout.compose.LayoutReference getReference();
+ property public final androidx.constraintlayout.compose.LayoutReference reference;
+ }
+
+ @androidx.compose.runtime.Stable public static final class ConstraintLayoutBaseScope.HorizontalAnchor {
+ method public androidx.constraintlayout.compose.LayoutReference component3();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor copy(Object id, int index, androidx.constraintlayout.compose.LayoutReference reference);
+ method public androidx.constraintlayout.compose.LayoutReference getReference();
+ property public final androidx.constraintlayout.compose.LayoutReference reference;
+ }
+
+ @androidx.compose.runtime.Stable public static final class ConstraintLayoutBaseScope.VerticalAnchor {
+ method public androidx.constraintlayout.compose.LayoutReference component3();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor copy(Object id, int index, androidx.constraintlayout.compose.LayoutReference reference);
+ method public androidx.constraintlayout.compose.LayoutReference getReference();
+ property public final androidx.constraintlayout.compose.LayoutReference reference;
+ }
+
+ public final class ConstraintLayoutKt {
+ method @androidx.compose.runtime.Composable public static inline void ConstraintLayout(optional androidx.compose.ui.Modifier modifier, optional int optimizationLevel, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? animateChangesSpec, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstraintLayoutScope,kotlin.Unit> content);
+ method @Deprecated @androidx.compose.runtime.Composable public static inline void ConstraintLayout(optional androidx.compose.ui.Modifier modifier, optional int optimizationLevel, optional boolean animateChanges, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstraintLayoutScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline void ConstraintLayout(androidx.constraintlayout.compose.ConstraintSet constraintSet, optional androidx.compose.ui.Modifier modifier, optional int optimizationLevel, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? animateChangesSpec, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @Deprecated @androidx.compose.runtime.Composable public static inline void ConstraintLayout(androidx.constraintlayout.compose.ConstraintSet constraintSet, optional androidx.compose.ui.Modifier modifier, optional int optimizationLevel, optional boolean animateChanges, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method public static androidx.constraintlayout.compose.ConstraintSet ConstraintSet(androidx.constraintlayout.compose.ConstraintSet extendConstraintSet, @org.intellij.lang.annotations.Language("json5") String jsonContent);
+ method public static androidx.constraintlayout.compose.ConstraintSet ConstraintSet(androidx.constraintlayout.compose.ConstraintSet extendConstraintSet, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstraintSetScope,kotlin.Unit> description);
+ method public static androidx.constraintlayout.compose.ConstraintSet ConstraintSet(@org.intellij.lang.annotations.Language("json5") String jsonContent);
+ method @androidx.compose.runtime.Composable public static androidx.constraintlayout.compose.ConstraintSet ConstraintSet(@org.intellij.lang.annotations.Language("json5") String content, optional @org.intellij.lang.annotations.Language("json5") String? overrideVariables);
+ method public static androidx.constraintlayout.compose.ConstraintSet ConstraintSet(kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstraintSetScope,kotlin.Unit> description);
+ method public static androidx.constraintlayout.compose.Dimension.MaxCoercible atLeast(androidx.constraintlayout.compose.Dimension.Coercible, float dp);
+ method public static androidx.constraintlayout.compose.Dimension atLeast(androidx.constraintlayout.compose.Dimension.MinCoercible, float dp);
+ method @Deprecated public static androidx.constraintlayout.compose.Dimension atLeastWrapContent(androidx.constraintlayout.compose.Dimension.MinCoercible, float dp);
+ method public static androidx.constraintlayout.compose.Dimension.MinCoercible atMost(androidx.constraintlayout.compose.Dimension.Coercible, float dp);
+ method public static androidx.constraintlayout.compose.Dimension atMost(androidx.constraintlayout.compose.Dimension.MaxCoercible, float dp);
+ method public static androidx.constraintlayout.compose.Dimension.MaxCoercible getAtLeastWrapContent(androidx.constraintlayout.compose.Dimension.Coercible);
+ method public static androidx.constraintlayout.compose.Dimension getAtLeastWrapContent(androidx.constraintlayout.compose.Dimension.MinCoercible);
+ method public static androidx.constraintlayout.compose.Dimension.MinCoercible getAtMostWrapContent(androidx.constraintlayout.compose.Dimension.Coercible);
+ method public static androidx.constraintlayout.compose.Dimension getAtMostWrapContent(androidx.constraintlayout.compose.Dimension.MaxCoercible);
+ }
+
+ @androidx.compose.foundation.layout.LayoutScopeMarker public final class ConstraintLayoutScope extends androidx.constraintlayout.compose.ConstraintLayoutBaseScope {
+ method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier constrainAs(androidx.compose.ui.Modifier, androidx.constraintlayout.compose.ConstrainedLayoutReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRef();
+ method @androidx.compose.runtime.Stable public androidx.constraintlayout.compose.ConstraintLayoutScope.ConstrainedLayoutReferences createRefs();
+ }
+
+ public final class ConstraintLayoutScope.ConstrainedLayoutReferences {
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component1();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component10();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component11();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component12();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component13();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component14();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component15();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component16();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component2();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component3();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component4();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component5();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component6();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component7();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component8();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component9();
+ }
+
+ public final class ConstraintLayoutTagKt {
+ method public static Object? getConstraintLayoutId(androidx.compose.ui.layout.Measurable);
+ method public static Object? getConstraintLayoutTag(androidx.compose.ui.layout.Measurable);
+ method public static androidx.compose.ui.Modifier layoutId(androidx.compose.ui.Modifier, String layoutId, optional String? tag);
+ }
+
+ public interface ConstraintLayoutTagParentData {
+ method public String getConstraintLayoutId();
+ method public String getConstraintLayoutTag();
+ property public abstract String constraintLayoutId;
+ property public abstract String constraintLayoutTag;
+ }
+
+ @androidx.compose.runtime.Immutable @kotlin.jvm.JvmDefaultWithCompatibility public interface ConstraintSet {
+ method public void applyTo(androidx.constraintlayout.compose.State state, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables);
+ method public default void applyTo(androidx.constraintlayout.core.state.Transition transition, int type);
+ method public default boolean isDirty(java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables);
+ method public default androidx.constraintlayout.compose.ConstraintSet override(String name, float value);
+ }
+
+ public final class ConstraintSetRef {
+ method public androidx.constraintlayout.compose.ConstraintSetRef copy(String name);
+ }
+
+ @androidx.compose.foundation.layout.LayoutScopeMarker public final class ConstraintSetScope extends androidx.constraintlayout.compose.ConstraintLayoutBaseScope {
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRefFor(Object id);
+ method public androidx.constraintlayout.compose.ConstraintSetScope.ConstrainedLayoutReferences createRefsFor(java.lang.Object... ids);
+ }
+
+ public final class ConstraintSetScope.ConstrainedLayoutReferences {
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component1();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component10();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component11();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component12();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component13();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component14();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component15();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component16();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component2();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component3();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component4();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component5();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component6();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component7();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component8();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component9();
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class CurveFit {
+ method public String getName();
+ property public String name;
+ field public static final androidx.constraintlayout.compose.CurveFit.Companion Companion;
+ }
+
+ public static final class CurveFit.Companion {
+ method public androidx.constraintlayout.compose.CurveFit getLinear();
+ method public androidx.constraintlayout.compose.CurveFit getSpline();
+ property public final androidx.constraintlayout.compose.CurveFit Linear;
+ property public final androidx.constraintlayout.compose.CurveFit Spline;
+ }
+
+ @kotlin.jvm.JvmInline public final value class DebugFlags {
+ ctor public DebugFlags(optional boolean showBounds, optional boolean showPaths, optional boolean showKeyPositions);
+ method public boolean getShowBounds();
+ method public boolean getShowKeyPositions();
+ method public boolean getShowPaths();
+ property public final boolean showBounds;
+ property public final boolean showKeyPositions;
+ property public final boolean showPaths;
+ field public static final androidx.constraintlayout.compose.DebugFlags.Companion Companion;
+ }
+
+ public static final class DebugFlags.Companion {
+ method public int getAll();
+ method public int getNone();
+ property public final int All;
+ property public final int None;
+ }
+
+ public final class DesignElements {
+ method public void define(String name, kotlin.jvm.functions.Function2<? super java.lang.String,? super java.util.HashMap<java.lang.String,java.lang.String>,kotlin.Unit> function);
+ method public java.util.HashMap<java.lang.String,kotlin.jvm.functions.Function2<java.lang.String,java.util.HashMap<java.lang.String,java.lang.String>,kotlin.Unit>> getMap();
+ method public void setMap(java.util.HashMap<java.lang.String,kotlin.jvm.functions.Function2<java.lang.String,java.util.HashMap<java.lang.String,java.lang.String>,kotlin.Unit>>);
+ property public final java.util.HashMap<java.lang.String,kotlin.jvm.functions.Function2<java.lang.String,java.util.HashMap<java.lang.String,java.lang.String>,kotlin.Unit>> map;
+ field public static final androidx.constraintlayout.compose.DesignElements INSTANCE;
+ }
+
+ public interface DesignInfoProvider {
+ method public String getDesignInfo(int startX, int startY, String args);
+ }
+
+ public interface Dimension {
+ field public static final androidx.constraintlayout.compose.Dimension.Companion Companion;
+ }
+
+ public static interface Dimension.Coercible extends androidx.constraintlayout.compose.Dimension {
+ }
+
+ public static final class Dimension.Companion {
+ method public androidx.constraintlayout.compose.Dimension.Coercible getFillToConstraints();
+ method public androidx.constraintlayout.compose.Dimension getMatchParent();
+ method public androidx.constraintlayout.compose.Dimension.Coercible getPreferredWrapContent();
+ method public androidx.constraintlayout.compose.Dimension getWrapContent();
+ method public androidx.constraintlayout.compose.Dimension percent(float percent);
+ method public androidx.constraintlayout.compose.Dimension.MinCoercible preferredValue(float dp);
+ method public androidx.constraintlayout.compose.Dimension ratio(String ratio);
+ method public androidx.constraintlayout.compose.Dimension value(float dp);
+ property public final androidx.constraintlayout.compose.Dimension.Coercible fillToConstraints;
+ property public final androidx.constraintlayout.compose.Dimension matchParent;
+ property public final androidx.constraintlayout.compose.Dimension.Coercible preferredWrapContent;
+ property public final androidx.constraintlayout.compose.Dimension wrapContent;
+ }
+
+ public static interface Dimension.MaxCoercible extends androidx.constraintlayout.compose.Dimension {
+ }
+
+ public static interface Dimension.MinCoercible extends androidx.constraintlayout.compose.Dimension {
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class Easing {
+ method public String getName();
+ property public String name;
+ field public static final androidx.constraintlayout.compose.Easing.Companion Companion;
+ }
+
+ public static final class Easing.Companion {
+ method public androidx.constraintlayout.compose.Easing cubic(float x1, float y1, float x2, float y2);
+ method public androidx.constraintlayout.compose.Easing getAccelerate();
+ method public androidx.constraintlayout.compose.Easing getAnticipate();
+ method public androidx.constraintlayout.compose.Easing getDecelerate();
+ method public androidx.constraintlayout.compose.Easing getLinear();
+ method public androidx.constraintlayout.compose.Easing getOvershoot();
+ method public androidx.constraintlayout.compose.Easing getStandard();
+ property public final androidx.constraintlayout.compose.Easing Accelerate;
+ property public final androidx.constraintlayout.compose.Easing Anticipate;
+ property public final androidx.constraintlayout.compose.Easing Decelerate;
+ property public final androidx.constraintlayout.compose.Easing Linear;
+ property public final androidx.constraintlayout.compose.Easing Overshoot;
+ property public final androidx.constraintlayout.compose.Easing Standard;
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="MotionLayout API is experimental and it is likely to change.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMotionApi {
+ }
+
+ @androidx.compose.runtime.Immutable public final class FlowStyle {
+ field public static final androidx.constraintlayout.compose.FlowStyle.Companion Companion;
+ }
+
+ public static final class FlowStyle.Companion {
+ method public androidx.constraintlayout.compose.FlowStyle getPacked();
+ method public androidx.constraintlayout.compose.FlowStyle getSpread();
+ method public androidx.constraintlayout.compose.FlowStyle getSpreadInside();
+ property public final androidx.constraintlayout.compose.FlowStyle Packed;
+ property public final androidx.constraintlayout.compose.FlowStyle Spread;
+ property public final androidx.constraintlayout.compose.FlowStyle SpreadInside;
+ }
+
+ @kotlin.jvm.JvmInline public final value class GridFlag {
+ method public boolean isPlaceLayoutsOnSpansFirst();
+ method public infix int or(int other);
+ property public final boolean isPlaceLayoutsOnSpansFirst;
+ field public static final androidx.constraintlayout.compose.GridFlag.Companion Companion;
+ }
+
+ public static final class GridFlag.Companion {
+ method public int getNone();
+ method public int getPlaceLayoutsOnSpansFirst();
+ property public final int None;
+ property public final int PlaceLayoutsOnSpansFirst;
+ }
+
+ @androidx.compose.runtime.Immutable public final class HorizontalAlign {
+ field public static final androidx.constraintlayout.compose.HorizontalAlign.Companion Companion;
+ }
+
+ public static final class HorizontalAlign.Companion {
+ method public androidx.constraintlayout.compose.HorizontalAlign getCenter();
+ method public androidx.constraintlayout.compose.HorizontalAlign getEnd();
+ method public androidx.constraintlayout.compose.HorizontalAlign getStart();
+ property public final androidx.constraintlayout.compose.HorizontalAlign Center;
+ property public final androidx.constraintlayout.compose.HorizontalAlign End;
+ property public final androidx.constraintlayout.compose.HorizontalAlign Start;
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface HorizontalAnchorable {
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor anchor, optional float margin, optional float goneMargin);
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor, optional float margin, optional float goneMargin);
+ }
+
+ @androidx.compose.runtime.Stable public final class HorizontalChainReference extends androidx.constraintlayout.compose.LayoutReference {
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getAbsoluteLeft();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getAbsoluteRight();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getEnd();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getStart();
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor absoluteLeft;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor absoluteRight;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor end;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor start;
+ }
+
+ @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Stable public final class HorizontalChainScope {
+ method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteLeft();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteRight();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getEnd();
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference getParent();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getStart();
+ property public final androidx.constraintlayout.compose.VerticalAnchorable absoluteLeft;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable absoluteRight;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable end;
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference parent;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable start;
+ }
+
+ public final class InvalidationStrategy {
+ ctor public InvalidationStrategy(optional kotlin.jvm.functions.Function3<? super androidx.constraintlayout.compose.InvalidationStrategySpecification,? super androidx.compose.ui.unit.Constraints,? super androidx.compose.ui.unit.Constraints,java.lang.Boolean>? onIncomingConstraints, kotlin.jvm.functions.Function0<kotlin.Unit>? onObservedStateChange);
+ method public kotlin.jvm.functions.Function3<androidx.constraintlayout.compose.InvalidationStrategySpecification,androidx.compose.ui.unit.Constraints,androidx.compose.ui.unit.Constraints,java.lang.Boolean>? getOnIncomingConstraints();
+ method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnObservedStateChange();
+ property public final kotlin.jvm.functions.Function3<androidx.constraintlayout.compose.InvalidationStrategySpecification,androidx.compose.ui.unit.Constraints,androidx.compose.ui.unit.Constraints,java.lang.Boolean>? onIncomingConstraints;
+ property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onObservedStateChange;
+ field public static final androidx.constraintlayout.compose.InvalidationStrategy.Companion Companion;
+ }
+
+ public static final class InvalidationStrategy.Companion {
+ method public androidx.constraintlayout.compose.InvalidationStrategy getDefaultInvalidationStrategy();
+ property public final androidx.constraintlayout.compose.InvalidationStrategy DefaultInvalidationStrategy;
+ }
+
+ public final class InvalidationStrategySpecification {
+ method public boolean shouldInvalidateOnFixedHeight(long oldConstraints, long newConstraints, int skipCount, int threshold);
+ method public boolean shouldInvalidateOnFixedWidth(long oldConstraints, long newConstraints, int skipCount, int threshold);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyAttributeScope extends androidx.constraintlayout.compose.BaseKeyFrameScope {
+ method public float getAlpha();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getRotationZ();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public void setAlpha(float);
+ method public void setRotationX(float);
+ method public void setRotationY(float);
+ method public void setRotationZ(float);
+ method public void setScaleX(float);
+ method public void setScaleY(float);
+ method public void setTranslationX(float);
+ method public void setTranslationY(float);
+ method public void setTranslationZ(float);
+ property public final float alpha;
+ property public final float rotationX;
+ property public final float rotationY;
+ property public final float rotationZ;
+ property public final float scaleX;
+ property public final float scaleY;
+ property public final float translationX;
+ property public final float translationY;
+ property public final float translationZ;
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyAttributesScope extends androidx.constraintlayout.compose.BaseKeyFramesScope {
+ method public void frame(@IntRange(from=0L, to=100L) int frame, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyAttributeScope,kotlin.Unit> keyFrameContent);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyCycleScope extends androidx.constraintlayout.compose.BaseKeyFrameScope {
+ method public float getAlpha();
+ method public float getOffset();
+ method public float getPeriod();
+ method public float getPhase();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getRotationZ();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public void setAlpha(float);
+ method public void setOffset(float);
+ method public void setPeriod(float);
+ method public void setPhase(float);
+ method public void setRotationX(float);
+ method public void setRotationY(float);
+ method public void setRotationZ(float);
+ method public void setScaleX(float);
+ method public void setScaleY(float);
+ method public void setTranslationX(float);
+ method public void setTranslationY(float);
+ method public void setTranslationZ(float);
+ property public final float alpha;
+ property public final float offset;
+ property public final float period;
+ property public final float phase;
+ property public final float rotationX;
+ property public final float rotationY;
+ property public final float rotationZ;
+ property public final float scaleX;
+ property public final float scaleY;
+ property public final float translationX;
+ property public final float translationY;
+ property public final float translationZ;
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyCyclesScope extends androidx.constraintlayout.compose.BaseKeyFramesScope {
+ method public void frame(@IntRange(from=0L, to=100L) int frame, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyCycleScope,kotlin.Unit> keyFrameContent);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyPositionScope extends androidx.constraintlayout.compose.BaseKeyFrameScope {
+ method public androidx.constraintlayout.compose.CurveFit? getCurveFit();
+ method public float getPercentHeight();
+ method public float getPercentWidth();
+ method public float getPercentX();
+ method public float getPercentY();
+ method public void setCurveFit(androidx.constraintlayout.compose.CurveFit?);
+ method public void setPercentHeight(float);
+ method public void setPercentWidth(float);
+ method public void setPercentX(float);
+ method public void setPercentY(float);
+ property public final androidx.constraintlayout.compose.CurveFit? curveFit;
+ property public final float percentHeight;
+ property public final float percentWidth;
+ property public final float percentX;
+ property public final float percentY;
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyPositionsScope extends androidx.constraintlayout.compose.BaseKeyFramesScope {
+ method public void frame(@IntRange(from=0L, to=100L) int frame, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyPositionScope,kotlin.Unit> keyFrameContent);
+ method public androidx.constraintlayout.compose.RelativePosition getType();
+ method public void setType(androidx.constraintlayout.compose.RelativePosition);
+ property public final androidx.constraintlayout.compose.RelativePosition type;
+ }
+
+ public enum LayoutInfoFlags {
+ enum_constant public static final androidx.constraintlayout.compose.LayoutInfoFlags BOUNDS;
+ enum_constant public static final androidx.constraintlayout.compose.LayoutInfoFlags NONE;
+ }
+
+ public interface LayoutInformationReceiver {
+ method public androidx.constraintlayout.compose.MotionLayoutDebugFlags getForcedDrawDebug();
+ method public int getForcedHeight();
+ method public float getForcedProgress();
+ method public int getForcedWidth();
+ method public androidx.constraintlayout.compose.LayoutInfoFlags getLayoutInformationMode();
+ method public void onNewProgress(float progress);
+ method public void resetForcedProgress();
+ method public void setLayoutInformation(String information);
+ method public void setUpdateFlag(androidx.compose.runtime.MutableState<java.lang.Long> needsUpdate);
+ }
+
+ @androidx.compose.runtime.Stable public abstract class LayoutReference {
+ }
+
+ public final class MotionCarouselKt {
+ method @androidx.compose.runtime.Composable public static void ItemHolder(int i, String slotPrefix, boolean showSlot, kotlin.jvm.functions.Function0<kotlin.Unit> function);
+ method @androidx.compose.runtime.Composable public static void MotionCarousel(androidx.constraintlayout.compose.MotionScene motionScene, int initialSlotIndex, int numSlots, optional String backwardTransition, optional String forwardTransition, optional String slotPrefix, optional boolean showSlots, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionCarouselScope,kotlin.Unit> content);
+ method public static inline <T> void items(androidx.constraintlayout.compose.MotionCarouselScope, java.util.List<? extends T> items, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> itemContent);
+ method public static inline <T> void itemsWithProperties(androidx.constraintlayout.compose.MotionCarouselScope, java.util.List<? extends T> items, kotlin.jvm.functions.Function2<? super T,? super androidx.compose.runtime.State<androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties>,kotlin.Unit> itemContent);
+ }
+
+ public interface MotionCarouselScope {
+ method public void items(int count, kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> itemContent);
+ method public void itemsWithProperties(int count, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super androidx.compose.runtime.State<androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties>,kotlin.Unit> itemContent);
+ }
+
+ public interface MotionItemsProvider {
+ method public int count();
+ method public kotlin.jvm.functions.Function0<kotlin.Unit> getContent(int index);
+ method public kotlin.jvm.functions.Function0<kotlin.Unit> getContent(int index, androidx.compose.runtime.State<androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties> properties);
+ method public boolean hasItemsWithProperties();
+ }
+
+ public enum MotionLayoutDebugFlags {
+ enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags NONE;
+ enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags SHOW_ALL;
+ enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags UNKNOWN;
+ }
+
+ @Deprecated public enum MotionLayoutFlag {
+ enum_constant @Deprecated public static final androidx.constraintlayout.compose.MotionLayoutFlag Default;
+ enum_constant @Deprecated public static final androidx.constraintlayout.compose.MotionLayoutFlag FullMeasure;
+ }
+
+ public final class MotionLayoutKt {
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static inline void MotionLayout(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, float progress, optional androidx.compose.ui.Modifier modifier, optional androidx.constraintlayout.compose.Transition? transition, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, float progress, optional androidx.compose.ui.Modifier modifier, optional String transitionName, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, String? constraintSetName, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class MotionLayoutScope {
+ method public long customColor(String id, String name);
+ method public float customDistance(String id, String name);
+ method public float customFloat(String id, String name);
+ method public long customFontSize(String id, String name);
+ method public int customInt(String id, String name);
+ method public androidx.constraintlayout.compose.MotionLayoutScope.CustomProperties customProperties(String id);
+ method @Deprecated public long motionColor(String id, String name);
+ method @Deprecated public float motionDistance(String id, String name);
+ method @Deprecated public float motionFloat(String id, String name);
+ method @Deprecated public long motionFontSize(String id, String name);
+ method @Deprecated public int motionInt(String id, String name);
+ method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties> motionProperties(String id);
+ method @Deprecated public androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties motionProperties(String id, String tag);
+ method public androidx.compose.ui.Modifier onStartEndBoundsChanged(androidx.compose.ui.Modifier, Object layoutId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.geometry.Rect,? super androidx.compose.ui.geometry.Rect,kotlin.Unit> onBoundsChanged);
+ }
+
+ public final class MotionLayoutScope.CustomProperties {
+ method public long color(String name);
+ method public float distance(String name);
+ method public float float(String name);
+ method public long fontSize(String name);
+ method public int int(String name);
+ }
+
+ public final class MotionLayoutScope.MotionProperties {
+ method public long color(String name);
+ method public float distance(String name);
+ method public float float(String name);
+ method public long fontSize(String name);
+ method public String id();
+ method public int int(String name);
+ method public String? tag();
+ }
+
+ @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.constraintlayout.compose.ExperimentalMotionApi public interface MotionScene extends androidx.constraintlayout.core.state.CoreMotionScene {
+ method public androidx.constraintlayout.compose.ConstraintSet? getConstraintSetInstance(String name);
+ method public androidx.constraintlayout.compose.Transition? getTransitionInstance(String name);
+ }
+
+ public final class MotionSceneKt {
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static androidx.constraintlayout.compose.MotionScene MotionScene(@org.intellij.lang.annotations.Language("json5") String content);
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class MotionSceneScope {
+ method public androidx.constraintlayout.compose.ConstraintSetRef addConstraintSet(androidx.constraintlayout.compose.ConstraintSet constraintSet, optional String? name);
+ method public void addTransition(androidx.constraintlayout.compose.Transition transition, optional String? name);
+ method public androidx.constraintlayout.compose.ConstraintSetRef constraintSet(optional String? name, optional androidx.constraintlayout.compose.ConstraintSetRef? extendConstraintSet, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstraintSetScope,kotlin.Unit> constraintSetContent);
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRefFor(Object id);
+ method public androidx.constraintlayout.compose.MotionSceneScope.ConstrainedLayoutReferences createRefsFor(java.lang.Object... ids);
+ method public void customColor(androidx.constraintlayout.compose.ConstrainScope, String name, long value);
+ method public void customColor(androidx.constraintlayout.compose.KeyAttributeScope, String name, long value);
+ method public void customDistance(androidx.constraintlayout.compose.ConstrainScope, String name, float value);
+ method public void customDistance(androidx.constraintlayout.compose.KeyAttributeScope, String name, float value);
+ method public void customFloat(androidx.constraintlayout.compose.ConstrainScope, String name, float value);
+ method public void customFloat(androidx.constraintlayout.compose.KeyAttributeScope, String name, float value);
+ method public void customFontSize(androidx.constraintlayout.compose.ConstrainScope, String name, long value);
+ method public void customFontSize(androidx.constraintlayout.compose.KeyAttributeScope, String name, long value);
+ method public void customInt(androidx.constraintlayout.compose.ConstrainScope, String name, int value);
+ method public void customInt(androidx.constraintlayout.compose.KeyAttributeScope, String name, int value);
+ method public void defaultTransition(androidx.constraintlayout.compose.ConstraintSetRef from, androidx.constraintlayout.compose.ConstraintSetRef to, optional kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.TransitionScope,kotlin.Unit> transitionContent);
+ method public float getStaggeredWeight(androidx.constraintlayout.compose.ConstrainScope);
+ method public void setStaggeredWeight(androidx.constraintlayout.compose.ConstrainScope, float);
+ method public void transition(androidx.constraintlayout.compose.ConstraintSetRef from, androidx.constraintlayout.compose.ConstraintSetRef to, optional String? name, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.TransitionScope,kotlin.Unit> transitionContent);
+ }
+
+ public final class MotionSceneScope.ConstrainedLayoutReferences {
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component1();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component10();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component11();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component12();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component13();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component14();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component15();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component16();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component2();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component3();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component4();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component5();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component6();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component7();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component8();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component9();
+ }
+
+ public final class MotionSceneScopeKt {
+ method @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public static androidx.constraintlayout.compose.MotionScene MotionScene(kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionSceneScope,kotlin.Unit> motionSceneContent);
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class OnSwipe {
+ ctor public OnSwipe(androidx.constraintlayout.compose.ConstrainedLayoutReference anchor, androidx.constraintlayout.compose.SwipeSide side, androidx.constraintlayout.compose.SwipeDirection direction, optional float dragScale, optional float dragThreshold, optional androidx.constraintlayout.compose.ConstrainedLayoutReference? dragAround, optional androidx.constraintlayout.compose.ConstrainedLayoutReference? limitBoundsTo, optional androidx.constraintlayout.compose.SwipeTouchUp onTouchUp, optional androidx.constraintlayout.compose.SwipeMode mode);
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference getAnchor();
+ method public androidx.constraintlayout.compose.SwipeDirection getDirection();
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference? getDragAround();
+ method public float getDragScale();
+ method public float getDragThreshold();
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference? getLimitBoundsTo();
+ method public androidx.constraintlayout.compose.SwipeMode getMode();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getOnTouchUp();
+ method public androidx.constraintlayout.compose.SwipeSide getSide();
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference anchor;
+ property public final androidx.constraintlayout.compose.SwipeDirection direction;
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference? dragAround;
+ property public final float dragScale;
+ property public final float dragThreshold;
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference? limitBoundsTo;
+ property public final androidx.constraintlayout.compose.SwipeMode mode;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp onTouchUp;
+ property public final androidx.constraintlayout.compose.SwipeSide side;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class RelativePosition {
+ method public String getName();
+ property public String name;
+ field public static final androidx.constraintlayout.compose.RelativePosition.Companion Companion;
+ }
+
+ public static final class RelativePosition.Companion {
+ method public androidx.constraintlayout.compose.RelativePosition getDelta();
+ method public androidx.constraintlayout.compose.RelativePosition getParent();
+ method public androidx.constraintlayout.compose.RelativePosition getPath();
+ property public final androidx.constraintlayout.compose.RelativePosition Delta;
+ property public final androidx.constraintlayout.compose.RelativePosition Parent;
+ property public final androidx.constraintlayout.compose.RelativePosition Path;
+ }
+
+ @kotlin.jvm.JvmInline public final value class Skip {
+ ctor public Skip(@IntRange(from=0L) int position, @IntRange(from=1L) int size);
+ ctor public Skip(@IntRange(from=0L) int position, @IntRange(from=1L) int rows, @IntRange(from=1L) int columns);
+ method public String getDescription();
+ property public final String description;
+ }
+
+ @kotlin.jvm.JvmInline public final value class Span {
+ ctor public Span(@IntRange(from=0L) int position, @IntRange(from=1L) int size);
+ ctor public Span(@IntRange(from=0L) int position, @IntRange(from=1L) int rows, @IntRange(from=1L) int columns);
+ ctor public Span(String description);
+ method public String getDescription();
+ property public final String description;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class SpringBoundary {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.SpringBoundary.Companion Companion;
+ }
+
+ public static final class SpringBoundary.Companion {
+ method public androidx.constraintlayout.compose.SpringBoundary getBounceBoth();
+ method public androidx.constraintlayout.compose.SpringBoundary getBounceEnd();
+ method public androidx.constraintlayout.compose.SpringBoundary getBounceStart();
+ method public androidx.constraintlayout.compose.SpringBoundary getOvershoot();
+ property public final androidx.constraintlayout.compose.SpringBoundary BounceBoth;
+ property public final androidx.constraintlayout.compose.SpringBoundary BounceEnd;
+ property public final androidx.constraintlayout.compose.SpringBoundary BounceStart;
+ property public final androidx.constraintlayout.compose.SpringBoundary Overshoot;
+ }
+
+ public final class State extends androidx.constraintlayout.core.state.State {
+ ctor public State(androidx.compose.ui.unit.Density density);
+ method public androidx.compose.ui.unit.Density getDensity();
+ method @Deprecated public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
+ method public long getRootIncomingConstraints();
+ method @Deprecated public void setLayoutDirection(androidx.compose.ui.unit.LayoutDirection);
+ method public void setRootIncomingConstraints(long);
+ property public final androidx.compose.ui.unit.Density density;
+ property @Deprecated public final androidx.compose.ui.unit.LayoutDirection layoutDirection;
+ property public final long rootIncomingConstraints;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class SwipeDirection {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.SwipeDirection.Companion Companion;
+ }
+
+ public static final class SwipeDirection.Companion {
+ method public androidx.constraintlayout.compose.SwipeDirection getClockwise();
+ method public androidx.constraintlayout.compose.SwipeDirection getCounterclockwise();
+ method public androidx.constraintlayout.compose.SwipeDirection getDown();
+ method public androidx.constraintlayout.compose.SwipeDirection getEnd();
+ method public androidx.constraintlayout.compose.SwipeDirection getLeft();
+ method public androidx.constraintlayout.compose.SwipeDirection getRight();
+ method public androidx.constraintlayout.compose.SwipeDirection getStart();
+ method public androidx.constraintlayout.compose.SwipeDirection getUp();
+ property public final androidx.constraintlayout.compose.SwipeDirection Clockwise;
+ property public final androidx.constraintlayout.compose.SwipeDirection Counterclockwise;
+ property public final androidx.constraintlayout.compose.SwipeDirection Down;
+ property public final androidx.constraintlayout.compose.SwipeDirection End;
+ property public final androidx.constraintlayout.compose.SwipeDirection Left;
+ property public final androidx.constraintlayout.compose.SwipeDirection Right;
+ property public final androidx.constraintlayout.compose.SwipeDirection Start;
+ property public final androidx.constraintlayout.compose.SwipeDirection Up;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class SwipeMode {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.SwipeMode.Companion Companion;
+ }
+
+ public static final class SwipeMode.Companion {
+ method public androidx.constraintlayout.compose.SwipeMode getSpring();
+ method public androidx.constraintlayout.compose.SwipeMode getVelocity();
+ method public androidx.constraintlayout.compose.SwipeMode spring(optional float mass, optional float stiffness, optional float damping, optional float threshold, optional androidx.constraintlayout.compose.SpringBoundary boundary);
+ method public androidx.constraintlayout.compose.SwipeMode velocity(optional float maxVelocity, optional float maxAcceleration);
+ property public final androidx.constraintlayout.compose.SwipeMode Spring;
+ property public final androidx.constraintlayout.compose.SwipeMode Velocity;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class SwipeSide {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.SwipeSide.Companion Companion;
+ }
+
+ public static final class SwipeSide.Companion {
+ method public androidx.constraintlayout.compose.SwipeSide getBottom();
+ method public androidx.constraintlayout.compose.SwipeSide getEnd();
+ method public androidx.constraintlayout.compose.SwipeSide getLeft();
+ method public androidx.constraintlayout.compose.SwipeSide getMiddle();
+ method public androidx.constraintlayout.compose.SwipeSide getRight();
+ method public androidx.constraintlayout.compose.SwipeSide getStart();
+ method public androidx.constraintlayout.compose.SwipeSide getTop();
+ property public final androidx.constraintlayout.compose.SwipeSide Bottom;
+ property public final androidx.constraintlayout.compose.SwipeSide End;
+ property public final androidx.constraintlayout.compose.SwipeSide Left;
+ property public final androidx.constraintlayout.compose.SwipeSide Middle;
+ property public final androidx.constraintlayout.compose.SwipeSide Right;
+ property public final androidx.constraintlayout.compose.SwipeSide Start;
+ property public final androidx.constraintlayout.compose.SwipeSide Top;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class SwipeTouchUp {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.SwipeTouchUp.Companion Companion;
+ }
+
+ public static final class SwipeTouchUp.Companion {
+ method public androidx.constraintlayout.compose.SwipeTouchUp getAutoComplete();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getDecelerate();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getNeverCompleteEnd();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getNeverCompleteStart();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getStop();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getToEnd();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getToStart();
+ property public final androidx.constraintlayout.compose.SwipeTouchUp AutoComplete;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp Decelerate;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp NeverCompleteEnd;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp NeverCompleteStart;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp Stop;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp ToEnd;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp ToStart;
+ }
+
+ public final class ToolingUtilsKt {
+ method public static androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.constraintlayout.compose.DesignInfoProvider> getDesignInfoDataKey();
+ property public static final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.constraintlayout.compose.DesignInfoProvider> DesignInfoDataKey;
+ }
+
+ @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.constraintlayout.compose.ExperimentalMotionApi public interface Transition {
+ method public String getEndConstraintSetId();
+ method public String getStartConstraintSetId();
+ }
+
+ public final class TransitionKt {
+ method @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public static androidx.constraintlayout.compose.Transition Transition(@org.intellij.lang.annotations.Language("json5") String content);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class TransitionScope {
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRefFor(Object id);
+ method public float getMaxStaggerDelay();
+ method public androidx.constraintlayout.compose.Arc getMotionArc();
+ method public androidx.constraintlayout.compose.OnSwipe? getOnSwipe();
+ method public void keyAttributes(androidx.constraintlayout.compose.ConstrainedLayoutReference[] targets, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyAttributesScope,kotlin.Unit> keyAttributesContent);
+ method public void keyCycles(androidx.constraintlayout.compose.ConstrainedLayoutReference[] targets, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyCyclesScope,kotlin.Unit> keyCyclesContent);
+ method public void keyPositions(androidx.constraintlayout.compose.ConstrainedLayoutReference[] targets, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyPositionsScope,kotlin.Unit> keyPositionsContent);
+ method public void setMaxStaggerDelay(float);
+ method public void setMotionArc(androidx.constraintlayout.compose.Arc);
+ method public void setOnSwipe(androidx.constraintlayout.compose.OnSwipe?);
+ property public final float maxStaggerDelay;
+ property public final androidx.constraintlayout.compose.Arc motionArc;
+ property public final androidx.constraintlayout.compose.OnSwipe? onSwipe;
+ }
+
+ public final class TransitionScopeKt {
+ method @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public static androidx.constraintlayout.compose.Transition Transition(optional String from, optional String to, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.TransitionScope,kotlin.Unit> content);
+ }
+
+ @androidx.compose.runtime.Immutable public final class VerticalAlign {
+ field public static final androidx.constraintlayout.compose.VerticalAlign.Companion Companion;
+ }
+
+ public static final class VerticalAlign.Companion {
+ method public androidx.constraintlayout.compose.VerticalAlign getBaseline();
+ method public androidx.constraintlayout.compose.VerticalAlign getBottom();
+ method public androidx.constraintlayout.compose.VerticalAlign getCenter();
+ method public androidx.constraintlayout.compose.VerticalAlign getTop();
+ property public final androidx.constraintlayout.compose.VerticalAlign Baseline;
+ property public final androidx.constraintlayout.compose.VerticalAlign Bottom;
+ property public final androidx.constraintlayout.compose.VerticalAlign Center;
+ property public final androidx.constraintlayout.compose.VerticalAlign Top;
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface VerticalAnchorable {
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor anchor, optional float margin, optional float goneMargin);
+ }
+
+ @androidx.compose.runtime.Stable public final class VerticalChainReference extends androidx.constraintlayout.compose.LayoutReference {
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor getBottom();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor getTop();
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor bottom;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor top;
+ }
+
+ @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Stable public final class VerticalChainScope {
+ method public androidx.constraintlayout.compose.HorizontalAnchorable getBottom();
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference getParent();
+ method public androidx.constraintlayout.compose.HorizontalAnchorable getTop();
+ property public final androidx.constraintlayout.compose.HorizontalAnchorable bottom;
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference parent;
+ property public final androidx.constraintlayout.compose.HorizontalAnchorable top;
+ }
+
+ @androidx.compose.runtime.Immutable public final class Visibility {
+ field public static final androidx.constraintlayout.compose.Visibility.Companion Companion;
+ }
+
+ public static final class Visibility.Companion {
+ method public androidx.constraintlayout.compose.Visibility getGone();
+ method public androidx.constraintlayout.compose.Visibility getInvisible();
+ method public androidx.constraintlayout.compose.Visibility getVisible();
+ property public final androidx.constraintlayout.compose.Visibility Gone;
+ property public final androidx.constraintlayout.compose.Visibility Invisible;
+ property public final androidx.constraintlayout.compose.Visibility Visible;
+ }
+
+ @androidx.compose.runtime.Immutable public final class Wrap {
+ field public static final androidx.constraintlayout.compose.Wrap.Companion Companion;
+ }
+
+ public static final class Wrap.Companion {
+ method public androidx.constraintlayout.compose.Wrap getAligned();
+ method public androidx.constraintlayout.compose.Wrap getChain();
+ method public androidx.constraintlayout.compose.Wrap getNone();
+ property public final androidx.constraintlayout.compose.Wrap Aligned;
+ property public final androidx.constraintlayout.compose.Wrap Chain;
+ property public final androidx.constraintlayout.compose.Wrap None;
+ }
+
+}
+
diff --git a/activity/activity-compose/api/res-1.10.0-beta01.txt b/constraintlayout/constraintlayout-compose/api/res-1.1.0-beta01.txt
similarity index 100%
rename from activity/activity-compose/api/res-1.10.0-beta01.txt
rename to constraintlayout/constraintlayout-compose/api/res-1.1.0-beta01.txt
diff --git a/constraintlayout/constraintlayout-compose/api/restricted_1.1.0-beta01.txt b/constraintlayout/constraintlayout-compose/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..f2836bb
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,1103 @@
+// Signature format: 4.0
+package androidx.constraintlayout.compose {
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class Arc {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.Arc.Companion Companion;
+ }
+
+ public static final class Arc.Companion {
+ method public androidx.constraintlayout.compose.Arc getAbove();
+ method public androidx.constraintlayout.compose.Arc getBelow();
+ method public androidx.constraintlayout.compose.Arc getFlip();
+ method public androidx.constraintlayout.compose.Arc getNone();
+ method public androidx.constraintlayout.compose.Arc getStartHorizontal();
+ method public androidx.constraintlayout.compose.Arc getStartVertical();
+ property public final androidx.constraintlayout.compose.Arc Above;
+ property public final androidx.constraintlayout.compose.Arc Below;
+ property public final androidx.constraintlayout.compose.Arc Flip;
+ property public final androidx.constraintlayout.compose.Arc None;
+ property public final androidx.constraintlayout.compose.Arc StartHorizontal;
+ property public final androidx.constraintlayout.compose.Arc StartVertical;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public abstract sealed class BaseKeyFrameScope {
+ method protected final <E extends androidx.constraintlayout.compose.NamedPropertyOrValue> kotlin.properties.ObservableProperty<E> addNameOnPropertyChange(E initialValue, optional String? nameOverride);
+ method protected final <T> kotlin.properties.ObservableProperty<T> addOnPropertyChange(T initialValue, optional String? nameOverride);
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public abstract sealed class BaseKeyFramesScope {
+ method public final androidx.constraintlayout.compose.Easing getEasing();
+ method public final void setEasing(androidx.constraintlayout.compose.Easing);
+ property public final androidx.constraintlayout.compose.Easing easing;
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface BaselineAnchorable {
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor anchor, optional float margin, optional float goneMargin);
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor, optional float margin, optional float goneMargin);
+ }
+
+ @androidx.compose.runtime.Immutable public final class ChainStyle {
+ field public static final androidx.constraintlayout.compose.ChainStyle.Companion Companion;
+ }
+
+ public static final class ChainStyle.Companion {
+ method @androidx.compose.runtime.Stable public androidx.constraintlayout.compose.ChainStyle Packed(float bias);
+ method public androidx.constraintlayout.compose.ChainStyle getPacked();
+ method public androidx.constraintlayout.compose.ChainStyle getSpread();
+ method public androidx.constraintlayout.compose.ChainStyle getSpreadInside();
+ property public final androidx.constraintlayout.compose.ChainStyle Packed;
+ property public final androidx.constraintlayout.compose.ChainStyle Spread;
+ property public final androidx.constraintlayout.compose.ChainStyle SpreadInside;
+ }
+
+ @kotlin.PublishedApi internal enum CompositionSource {
+ enum_constant public static final androidx.constraintlayout.compose.CompositionSource Content;
+ enum_constant public static final androidx.constraintlayout.compose.CompositionSource Unknown;
+ }
+
+ @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Stable public final class ConstrainScope {
+ method public androidx.constraintlayout.compose.Dimension asDimension(float);
+ method public void centerAround(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor);
+ method public void centerAround(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor anchor);
+ method public void centerHorizontallyTo(androidx.constraintlayout.compose.ConstrainedLayoutReference other, optional @FloatRange(from=0.0, to=1.0) float bias);
+ method public void centerTo(androidx.constraintlayout.compose.ConstrainedLayoutReference other);
+ method public void centerVerticallyTo(androidx.constraintlayout.compose.ConstrainedLayoutReference other, optional @FloatRange(from=0.0, to=1.0) float bias);
+ method public void circular(androidx.constraintlayout.compose.ConstrainedLayoutReference other, float angle, float distance);
+ method public void clearConstraints();
+ method public void clearHorizontal();
+ method public void clearVertical();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteLeft();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteRight();
+ method public float getAlpha();
+ method public androidx.constraintlayout.compose.BaselineAnchorable getBaseline();
+ method public androidx.constraintlayout.compose.HorizontalAnchorable getBottom();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getEnd();
+ method public androidx.constraintlayout.compose.Dimension getHeight();
+ method public float getHorizontalBias();
+ method public float getHorizontalChainWeight();
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference getParent();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getRotationZ();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getStart();
+ method public androidx.constraintlayout.compose.HorizontalAnchorable getTop();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public float getVerticalBias();
+ method public float getVerticalChainWeight();
+ method public androidx.constraintlayout.compose.Visibility getVisibility();
+ method public androidx.constraintlayout.compose.Dimension getWidth();
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor top, androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor bottom, optional float topMargin, optional float bottomMargin, optional float topGoneMargin, optional float bottomGoneMargin, optional @FloatRange(from=0.0, to=1.0) float bias);
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor start, androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor top, androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor end, androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor bottom, optional float startMargin, optional float topMargin, optional float endMargin, optional float bottomMargin, optional float startGoneMargin, optional float topGoneMargin, optional float endGoneMargin, optional float bottomGoneMargin, optional @FloatRange(from=0.0, to=1.0) float horizontalBias, optional @FloatRange(from=0.0, to=1.0) float verticalBias);
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor start, androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor end, optional float startMargin, optional float endMargin, optional float startGoneMargin, optional float endGoneMargin, optional @FloatRange(from=0.0, to=1.0) float bias);
+ method public void resetDimensions();
+ method public void resetTransforms();
+ method public void setAlpha(float);
+ method public void setHeight(androidx.constraintlayout.compose.Dimension);
+ method public void setHorizontalBias(float);
+ method public void setHorizontalChainWeight(float);
+ method public void setPivotX(float);
+ method public void setPivotY(float);
+ method public void setRotationX(float);
+ method public void setRotationY(float);
+ method public void setRotationZ(float);
+ method public void setScaleX(float);
+ method public void setScaleY(float);
+ method public void setTranslationX(float);
+ method public void setTranslationY(float);
+ method public void setTranslationZ(float);
+ method public void setVerticalBias(float);
+ method public void setVerticalChainWeight(float);
+ method public void setVisibility(androidx.constraintlayout.compose.Visibility);
+ method public void setWidth(androidx.constraintlayout.compose.Dimension);
+ property public final androidx.constraintlayout.compose.VerticalAnchorable absoluteLeft;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable absoluteRight;
+ property public final float alpha;
+ property public final androidx.constraintlayout.compose.BaselineAnchorable baseline;
+ property public final androidx.constraintlayout.compose.HorizontalAnchorable bottom;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable end;
+ property public final androidx.constraintlayout.compose.Dimension height;
+ property public final float horizontalBias;
+ property public final float horizontalChainWeight;
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference parent;
+ property public final float pivotX;
+ property public final float pivotY;
+ property public final float rotationX;
+ property public final float rotationY;
+ property public final float rotationZ;
+ property public final float scaleX;
+ property public final float scaleY;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable start;
+ property public final androidx.constraintlayout.compose.HorizontalAnchorable top;
+ property public final float translationX;
+ property public final float translationY;
+ property public final float translationZ;
+ property public final float verticalBias;
+ property public final float verticalChainWeight;
+ property public final androidx.constraintlayout.compose.Visibility visibility;
+ property public final androidx.constraintlayout.compose.Dimension width;
+ }
+
+ @androidx.compose.runtime.Stable public final class ConstrainedLayoutReference extends androidx.constraintlayout.compose.LayoutReference {
+ ctor public ConstrainedLayoutReference(Object id);
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getAbsoluteLeft();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getAbsoluteRight();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor getBaseline();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor getBottom();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getEnd();
+ method public Object getId();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getStart();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor getTop();
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor absoluteLeft;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor absoluteRight;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor baseline;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor bottom;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor end;
+ property public Object id;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor start;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor top;
+ }
+
+ public abstract class ConstraintLayoutBaseScope {
+ ctor public ConstraintLayoutBaseScope();
+ method public final void applyTo(androidx.constraintlayout.compose.State state);
+ method public final androidx.constraintlayout.compose.ConstrainScope constrain(androidx.constraintlayout.compose.ConstrainedLayoutReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
+ method public final void constrain(androidx.constraintlayout.compose.ConstrainedLayoutReference[] refs, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
+ method public final androidx.constraintlayout.compose.HorizontalChainScope constrain(androidx.constraintlayout.compose.HorizontalChainReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.HorizontalChainScope,kotlin.Unit> constrainBlock);
+ method public final androidx.constraintlayout.compose.VerticalChainScope constrain(androidx.constraintlayout.compose.VerticalChainReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.VerticalChainScope,kotlin.Unit> constrainBlock);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createAbsoluteLeftBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createAbsoluteRightBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createBottomBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createColumn(androidx.constraintlayout.compose.LayoutReference[] elements, optional float spacing, optional float[] weights);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createEndBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createFlow(androidx.constraintlayout.compose.LayoutReference?[] elements, optional boolean flowVertically, optional float verticalGap, optional float horizontalGap, optional int maxElement, optional float padding, optional androidx.constraintlayout.compose.Wrap wrapMode, optional androidx.constraintlayout.compose.VerticalAlign verticalAlign, optional androidx.constraintlayout.compose.HorizontalAlign horizontalAlign, optional float horizontalFlowBias, optional float verticalFlowBias, optional androidx.constraintlayout.compose.FlowStyle verticalStyle, optional androidx.constraintlayout.compose.FlowStyle horizontalStyle);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createFlow(androidx.constraintlayout.compose.LayoutReference?[] elements, optional boolean flowVertically, optional float verticalGap, optional float horizontalGap, optional int maxElement, optional float paddingHorizontal, optional float paddingVertical, optional androidx.constraintlayout.compose.Wrap wrapMode, optional androidx.constraintlayout.compose.VerticalAlign verticalAlign, optional androidx.constraintlayout.compose.HorizontalAlign horizontalAlign, optional float horizontalFlowBias, optional float verticalFlowBias, optional androidx.constraintlayout.compose.FlowStyle verticalStyle, optional androidx.constraintlayout.compose.FlowStyle horizontalStyle);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createFlow(androidx.constraintlayout.compose.LayoutReference?[] elements, optional boolean flowVertically, optional float verticalGap, optional float horizontalGap, optional int maxElement, optional float paddingLeft, optional float paddingTop, optional float paddingRight, optional float paddingBottom, optional androidx.constraintlayout.compose.Wrap wrapMode, optional androidx.constraintlayout.compose.VerticalAlign verticalAlign, optional androidx.constraintlayout.compose.HorizontalAlign horizontalAlign, optional float horizontalFlowBias, optional float verticalFlowBias, optional androidx.constraintlayout.compose.FlowStyle verticalStyle, optional androidx.constraintlayout.compose.FlowStyle horizontalStyle);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createGrid(androidx.constraintlayout.compose.LayoutReference[] elements, @IntRange(from=1L) int rows, @IntRange(from=1L) int columns, optional boolean isHorizontalArrangement, optional float verticalSpacing, optional float horizontalSpacing, optional float[] rowWeights, optional float[] columnWeights, optional androidx.constraintlayout.compose.Skip[] skips, optional androidx.constraintlayout.compose.Span[] spans, optional int flags);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromAbsoluteLeft(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromAbsoluteLeft(float fraction);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromAbsoluteRight(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromAbsoluteRight(float fraction);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createGuidelineFromBottom(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createGuidelineFromBottom(float fraction);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromEnd(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromEnd(float fraction);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromStart(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createGuidelineFromStart(float fraction);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createGuidelineFromTop(float offset);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createGuidelineFromTop(float fraction);
+ method public final androidx.constraintlayout.compose.HorizontalChainReference createHorizontalChain(androidx.constraintlayout.compose.LayoutReference[] elements, optional androidx.constraintlayout.compose.ChainStyle chainStyle);
+ method public final androidx.constraintlayout.compose.ConstrainedLayoutReference createRow(androidx.constraintlayout.compose.LayoutReference[] elements, optional float spacing, optional float[] weights);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createStartBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createTopBarrier(androidx.constraintlayout.compose.LayoutReference[] elements, optional float margin);
+ method public final androidx.constraintlayout.compose.VerticalChainReference createVerticalChain(androidx.constraintlayout.compose.LayoutReference[] elements, optional androidx.constraintlayout.compose.ChainStyle chainStyle);
+ method @Deprecated protected final java.util.List<kotlin.jvm.functions.Function1<androidx.constraintlayout.compose.State,kotlin.Unit>> getTasks();
+ method public void reset();
+ method public final androidx.constraintlayout.compose.LayoutReference withChainParams(androidx.constraintlayout.compose.LayoutReference, optional float startMargin, optional float topMargin, optional float endMargin, optional float bottomMargin, optional float startGoneMargin, optional float topGoneMargin, optional float endGoneMargin, optional float bottomGoneMargin, optional float weight);
+ method public final androidx.constraintlayout.compose.LayoutReference withHorizontalChainParams(androidx.constraintlayout.compose.LayoutReference, optional float startMargin, optional float endMargin, optional float startGoneMargin, optional float endGoneMargin, optional float weight);
+ method public final androidx.constraintlayout.compose.LayoutReference withVerticalChainParams(androidx.constraintlayout.compose.LayoutReference, optional float topMargin, optional float bottomMargin, optional float topGoneMargin, optional float bottomGoneMargin, optional float weight);
+ property @Deprecated protected final java.util.List<kotlin.jvm.functions.Function1<androidx.constraintlayout.compose.State,kotlin.Unit>> tasks;
+ field @kotlin.PublishedApi internal final androidx.constraintlayout.core.parser.CLObject containerObject;
+ field @kotlin.PublishedApi internal int helpersHashCode;
+ }
+
+ @androidx.compose.runtime.Stable public static final class ConstraintLayoutBaseScope.BaselineAnchor {
+ method public androidx.constraintlayout.compose.LayoutReference component2();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor copy(Object id, androidx.constraintlayout.compose.LayoutReference reference);
+ method public androidx.constraintlayout.compose.LayoutReference getReference();
+ property public final androidx.constraintlayout.compose.LayoutReference reference;
+ }
+
+ @androidx.compose.runtime.Stable public static final class ConstraintLayoutBaseScope.HorizontalAnchor {
+ method public androidx.constraintlayout.compose.LayoutReference component3();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor copy(Object id, int index, androidx.constraintlayout.compose.LayoutReference reference);
+ method public androidx.constraintlayout.compose.LayoutReference getReference();
+ property public final androidx.constraintlayout.compose.LayoutReference reference;
+ }
+
+ @androidx.compose.runtime.Stable public static final class ConstraintLayoutBaseScope.VerticalAnchor {
+ method public androidx.constraintlayout.compose.LayoutReference component3();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor copy(Object id, int index, androidx.constraintlayout.compose.LayoutReference reference);
+ method public androidx.constraintlayout.compose.LayoutReference getReference();
+ property public final androidx.constraintlayout.compose.LayoutReference reference;
+ }
+
+ public final class ConstraintLayoutKt {
+ method @androidx.compose.runtime.Composable public static inline void ConstraintLayout(optional androidx.compose.ui.Modifier modifier, optional int optimizationLevel, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? animateChangesSpec, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstraintLayoutScope,kotlin.Unit> content);
+ method @Deprecated @androidx.compose.runtime.Composable public static inline void ConstraintLayout(optional androidx.compose.ui.Modifier modifier, optional int optimizationLevel, optional boolean animateChanges, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstraintLayoutScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline void ConstraintLayout(androidx.constraintlayout.compose.ConstraintSet constraintSet, optional androidx.compose.ui.Modifier modifier, optional int optimizationLevel, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? animateChangesSpec, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @Deprecated @androidx.compose.runtime.Composable public static inline void ConstraintLayout(androidx.constraintlayout.compose.ConstraintSet constraintSet, optional androidx.compose.ui.Modifier modifier, optional int optimizationLevel, optional boolean animateChanges, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method public static androidx.constraintlayout.compose.ConstraintSet ConstraintSet(androidx.constraintlayout.compose.ConstraintSet extendConstraintSet, @org.intellij.lang.annotations.Language("json5") String jsonContent);
+ method public static androidx.constraintlayout.compose.ConstraintSet ConstraintSet(androidx.constraintlayout.compose.ConstraintSet extendConstraintSet, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstraintSetScope,kotlin.Unit> description);
+ method public static androidx.constraintlayout.compose.ConstraintSet ConstraintSet(@org.intellij.lang.annotations.Language("json5") String jsonContent);
+ method @androidx.compose.runtime.Composable public static androidx.constraintlayout.compose.ConstraintSet ConstraintSet(@org.intellij.lang.annotations.Language("json5") String content, optional @org.intellij.lang.annotations.Language("json5") String? overrideVariables);
+ method public static androidx.constraintlayout.compose.ConstraintSet ConstraintSet(kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstraintSetScope,kotlin.Unit> description);
+ method public static androidx.constraintlayout.compose.Dimension.MaxCoercible atLeast(androidx.constraintlayout.compose.Dimension.Coercible, float dp);
+ method public static androidx.constraintlayout.compose.Dimension atLeast(androidx.constraintlayout.compose.Dimension.MinCoercible, float dp);
+ method @Deprecated public static androidx.constraintlayout.compose.Dimension atLeastWrapContent(androidx.constraintlayout.compose.Dimension.MinCoercible, float dp);
+ method public static androidx.constraintlayout.compose.Dimension.MinCoercible atMost(androidx.constraintlayout.compose.Dimension.Coercible, float dp);
+ method public static androidx.constraintlayout.compose.Dimension atMost(androidx.constraintlayout.compose.Dimension.MaxCoercible, float dp);
+ method public static androidx.constraintlayout.compose.Dimension.MaxCoercible getAtLeastWrapContent(androidx.constraintlayout.compose.Dimension.Coercible);
+ method public static androidx.constraintlayout.compose.Dimension getAtLeastWrapContent(androidx.constraintlayout.compose.Dimension.MinCoercible);
+ method public static androidx.constraintlayout.compose.Dimension.MinCoercible getAtMostWrapContent(androidx.constraintlayout.compose.Dimension.Coercible);
+ method public static androidx.constraintlayout.compose.Dimension getAtMostWrapContent(androidx.constraintlayout.compose.Dimension.MaxCoercible);
+ }
+
+ @androidx.compose.foundation.layout.LayoutScopeMarker public final class ConstraintLayoutScope extends androidx.constraintlayout.compose.ConstraintLayoutBaseScope {
+ ctor @kotlin.PublishedApi internal ConstraintLayoutScope();
+ method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier constrainAs(androidx.compose.ui.Modifier, androidx.constraintlayout.compose.ConstrainedLayoutReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRef();
+ method @androidx.compose.runtime.Stable public androidx.constraintlayout.compose.ConstraintLayoutScope.ConstrainedLayoutReferences createRefs();
+ field @kotlin.PublishedApi internal boolean isAnimateChanges;
+ }
+
+ public final class ConstraintLayoutScope.ConstrainedLayoutReferences {
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component1();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component10();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component11();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component12();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component13();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component14();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component15();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component16();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component2();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component3();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component4();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component5();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component6();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component7();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component8();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component9();
+ }
+
+ public final class ConstraintLayoutTagKt {
+ method public static Object? getConstraintLayoutId(androidx.compose.ui.layout.Measurable);
+ method public static Object? getConstraintLayoutTag(androidx.compose.ui.layout.Measurable);
+ method public static androidx.compose.ui.Modifier layoutId(androidx.compose.ui.Modifier, String layoutId, optional String? tag);
+ }
+
+ public interface ConstraintLayoutTagParentData {
+ method public String getConstraintLayoutId();
+ method public String getConstraintLayoutTag();
+ property public abstract String constraintLayoutId;
+ property public abstract String constraintLayoutTag;
+ }
+
+ @androidx.compose.runtime.Immutable @kotlin.jvm.JvmDefaultWithCompatibility public interface ConstraintSet {
+ method public void applyTo(androidx.constraintlayout.compose.State state, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables);
+ method public default void applyTo(androidx.constraintlayout.core.state.Transition transition, int type);
+ method public default boolean isDirty(java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables);
+ method public default androidx.constraintlayout.compose.ConstraintSet override(String name, float value);
+ }
+
+ @kotlin.PublishedApi internal final class ConstraintSetForInlineDsl implements androidx.constraintlayout.compose.ConstraintSet androidx.compose.runtime.RememberObserver {
+ ctor public ConstraintSetForInlineDsl(androidx.constraintlayout.compose.ConstraintLayoutScope scope);
+ method public void applyTo(androidx.constraintlayout.compose.State state, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables);
+ method public boolean getKnownDirty();
+ method public androidx.constraintlayout.compose.ConstraintLayoutScope getScope();
+ method public void onAbandoned();
+ method public void onForgotten();
+ method public void onRemembered();
+ method public void setKnownDirty(boolean);
+ property public final boolean knownDirty;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutScope scope;
+ }
+
+ public final class ConstraintSetRef {
+ method public androidx.constraintlayout.compose.ConstraintSetRef copy(String name);
+ }
+
+ @androidx.compose.foundation.layout.LayoutScopeMarker public final class ConstraintSetScope extends androidx.constraintlayout.compose.ConstraintLayoutBaseScope {
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRefFor(Object id);
+ method public androidx.constraintlayout.compose.ConstraintSetScope.ConstrainedLayoutReferences createRefsFor(java.lang.Object... ids);
+ }
+
+ public final class ConstraintSetScope.ConstrainedLayoutReferences {
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component1();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component10();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component11();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component12();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component13();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component14();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component15();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component16();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component2();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component3();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component4();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component5();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component6();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component7();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component8();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component9();
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class CurveFit {
+ method public String getName();
+ property public String name;
+ field public static final androidx.constraintlayout.compose.CurveFit.Companion Companion;
+ }
+
+ public static final class CurveFit.Companion {
+ method public androidx.constraintlayout.compose.CurveFit getLinear();
+ method public androidx.constraintlayout.compose.CurveFit getSpline();
+ property public final androidx.constraintlayout.compose.CurveFit Linear;
+ property public final androidx.constraintlayout.compose.CurveFit Spline;
+ }
+
+ @kotlin.jvm.JvmInline public final value class DebugFlags {
+ ctor public DebugFlags(optional boolean showBounds, optional boolean showPaths, optional boolean showKeyPositions);
+ method public boolean getShowBounds();
+ method public boolean getShowKeyPositions();
+ method public boolean getShowPaths();
+ property public final boolean showBounds;
+ property public final boolean showKeyPositions;
+ property public final boolean showPaths;
+ field public static final androidx.constraintlayout.compose.DebugFlags.Companion Companion;
+ }
+
+ public static final class DebugFlags.Companion {
+ method public int getAll();
+ method public int getNone();
+ property public final int All;
+ property public final int None;
+ }
+
+ public final class DesignElements {
+ method public void define(String name, kotlin.jvm.functions.Function2<? super java.lang.String,? super java.util.HashMap<java.lang.String,java.lang.String>,kotlin.Unit> function);
+ method public java.util.HashMap<java.lang.String,kotlin.jvm.functions.Function2<java.lang.String,java.util.HashMap<java.lang.String,java.lang.String>,kotlin.Unit>> getMap();
+ method public void setMap(java.util.HashMap<java.lang.String,kotlin.jvm.functions.Function2<java.lang.String,java.util.HashMap<java.lang.String,java.lang.String>,kotlin.Unit>>);
+ property public final java.util.HashMap<java.lang.String,kotlin.jvm.functions.Function2<java.lang.String,java.util.HashMap<java.lang.String,java.lang.String>,kotlin.Unit>> map;
+ field public static final androidx.constraintlayout.compose.DesignElements INSTANCE;
+ }
+
+ public interface DesignInfoProvider {
+ method public String getDesignInfo(int startX, int startY, String args);
+ }
+
+ public interface Dimension {
+ field public static final androidx.constraintlayout.compose.Dimension.Companion Companion;
+ }
+
+ public static interface Dimension.Coercible extends androidx.constraintlayout.compose.Dimension {
+ }
+
+ public static final class Dimension.Companion {
+ method public androidx.constraintlayout.compose.Dimension.Coercible getFillToConstraints();
+ method public androidx.constraintlayout.compose.Dimension getMatchParent();
+ method public androidx.constraintlayout.compose.Dimension.Coercible getPreferredWrapContent();
+ method public androidx.constraintlayout.compose.Dimension getWrapContent();
+ method public androidx.constraintlayout.compose.Dimension percent(float percent);
+ method public androidx.constraintlayout.compose.Dimension.MinCoercible preferredValue(float dp);
+ method public androidx.constraintlayout.compose.Dimension ratio(String ratio);
+ method public androidx.constraintlayout.compose.Dimension value(float dp);
+ property public final androidx.constraintlayout.compose.Dimension.Coercible fillToConstraints;
+ property public final androidx.constraintlayout.compose.Dimension matchParent;
+ property public final androidx.constraintlayout.compose.Dimension.Coercible preferredWrapContent;
+ property public final androidx.constraintlayout.compose.Dimension wrapContent;
+ }
+
+ public static interface Dimension.MaxCoercible extends androidx.constraintlayout.compose.Dimension {
+ }
+
+ public static interface Dimension.MinCoercible extends androidx.constraintlayout.compose.Dimension {
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class Easing {
+ method public String getName();
+ property public String name;
+ field public static final androidx.constraintlayout.compose.Easing.Companion Companion;
+ }
+
+ public static final class Easing.Companion {
+ method public androidx.constraintlayout.compose.Easing cubic(float x1, float y1, float x2, float y2);
+ method public androidx.constraintlayout.compose.Easing getAccelerate();
+ method public androidx.constraintlayout.compose.Easing getAnticipate();
+ method public androidx.constraintlayout.compose.Easing getDecelerate();
+ method public androidx.constraintlayout.compose.Easing getLinear();
+ method public androidx.constraintlayout.compose.Easing getOvershoot();
+ method public androidx.constraintlayout.compose.Easing getStandard();
+ property public final androidx.constraintlayout.compose.Easing Accelerate;
+ property public final androidx.constraintlayout.compose.Easing Anticipate;
+ property public final androidx.constraintlayout.compose.Easing Decelerate;
+ property public final androidx.constraintlayout.compose.Easing Linear;
+ property public final androidx.constraintlayout.compose.Easing Overshoot;
+ property public final androidx.constraintlayout.compose.Easing Standard;
+ }
+
+ @kotlin.PublishedApi internal abstract class EditableJSONLayout implements androidx.constraintlayout.compose.LayoutInformationReceiver {
+ ctor public EditableJSONLayout(@org.intellij.lang.annotations.Language("json5") String content);
+ method public final String getCurrentContent();
+ method public final String? getDebugName();
+ method public androidx.constraintlayout.compose.MotionLayoutDebugFlags getForcedDrawDebug();
+ method public int getForcedHeight();
+ method public int getForcedWidth();
+ method public final String getLayoutInformation();
+ method public androidx.constraintlayout.compose.LayoutInfoFlags getLayoutInformationMode();
+ method protected final void initialization();
+ method protected final void onDrawDebug(int debugMode);
+ method protected final void onLayoutInformation(int mode);
+ method protected void onNewContent(String content);
+ method public final void onNewDimensions(int width, int height);
+ method public final void setCurrentContent(String content);
+ method public final void setDebugName(String? name);
+ method public void setLayoutInformation(String information);
+ method public void setUpdateFlag(androidx.compose.runtime.MutableState<java.lang.Long> needsUpdate);
+ method protected final void signalUpdate();
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="MotionLayout API is experimental and it is likely to change.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMotionApi {
+ }
+
+ @androidx.compose.runtime.Immutable public final class FlowStyle {
+ field public static final androidx.constraintlayout.compose.FlowStyle.Companion Companion;
+ }
+
+ public static final class FlowStyle.Companion {
+ method public androidx.constraintlayout.compose.FlowStyle getPacked();
+ method public androidx.constraintlayout.compose.FlowStyle getSpread();
+ method public androidx.constraintlayout.compose.FlowStyle getSpreadInside();
+ property public final androidx.constraintlayout.compose.FlowStyle Packed;
+ property public final androidx.constraintlayout.compose.FlowStyle Spread;
+ property public final androidx.constraintlayout.compose.FlowStyle SpreadInside;
+ }
+
+ @kotlin.jvm.JvmInline public final value class GridFlag {
+ method public boolean isPlaceLayoutsOnSpansFirst();
+ method public infix int or(int other);
+ property public final boolean isPlaceLayoutsOnSpansFirst;
+ field public static final androidx.constraintlayout.compose.GridFlag.Companion Companion;
+ }
+
+ public static final class GridFlag.Companion {
+ method public int getNone();
+ method public int getPlaceLayoutsOnSpansFirst();
+ property public final int None;
+ property public final int PlaceLayoutsOnSpansFirst;
+ }
+
+ @androidx.compose.runtime.Immutable public final class HorizontalAlign {
+ field public static final androidx.constraintlayout.compose.HorizontalAlign.Companion Companion;
+ }
+
+ public static final class HorizontalAlign.Companion {
+ method public androidx.constraintlayout.compose.HorizontalAlign getCenter();
+ method public androidx.constraintlayout.compose.HorizontalAlign getEnd();
+ method public androidx.constraintlayout.compose.HorizontalAlign getStart();
+ property public final androidx.constraintlayout.compose.HorizontalAlign Center;
+ property public final androidx.constraintlayout.compose.HorizontalAlign End;
+ property public final androidx.constraintlayout.compose.HorizontalAlign Start;
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface HorizontalAnchorable {
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor anchor, optional float margin, optional float goneMargin);
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor, optional float margin, optional float goneMargin);
+ }
+
+ @androidx.compose.runtime.Stable public final class HorizontalChainReference extends androidx.constraintlayout.compose.LayoutReference {
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getAbsoluteLeft();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getAbsoluteRight();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getEnd();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor getStart();
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor absoluteLeft;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor absoluteRight;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor end;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor start;
+ }
+
+ @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Stable public final class HorizontalChainScope {
+ method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteLeft();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteRight();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getEnd();
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference getParent();
+ method public androidx.constraintlayout.compose.VerticalAnchorable getStart();
+ property public final androidx.constraintlayout.compose.VerticalAnchorable absoluteLeft;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable absoluteRight;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable end;
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference parent;
+ property public final androidx.constraintlayout.compose.VerticalAnchorable start;
+ }
+
+ public final class InvalidationStrategy {
+ ctor public InvalidationStrategy(optional kotlin.jvm.functions.Function3<? super androidx.constraintlayout.compose.InvalidationStrategySpecification,? super androidx.compose.ui.unit.Constraints,? super androidx.compose.ui.unit.Constraints,java.lang.Boolean>? onIncomingConstraints, kotlin.jvm.functions.Function0<kotlin.Unit>? onObservedStateChange);
+ method public kotlin.jvm.functions.Function3<androidx.constraintlayout.compose.InvalidationStrategySpecification,androidx.compose.ui.unit.Constraints,androidx.compose.ui.unit.Constraints,java.lang.Boolean>? getOnIncomingConstraints();
+ method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnObservedStateChange();
+ property public final kotlin.jvm.functions.Function3<androidx.constraintlayout.compose.InvalidationStrategySpecification,androidx.compose.ui.unit.Constraints,androidx.compose.ui.unit.Constraints,java.lang.Boolean>? onIncomingConstraints;
+ property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onObservedStateChange;
+ field public static final androidx.constraintlayout.compose.InvalidationStrategy.Companion Companion;
+ }
+
+ public static final class InvalidationStrategy.Companion {
+ method public androidx.constraintlayout.compose.InvalidationStrategy getDefaultInvalidationStrategy();
+ property public final androidx.constraintlayout.compose.InvalidationStrategy DefaultInvalidationStrategy;
+ }
+
+ public final class InvalidationStrategySpecification {
+ method public boolean shouldInvalidateOnFixedHeight(long oldConstraints, long newConstraints, int skipCount, int threshold);
+ method public boolean shouldInvalidateOnFixedWidth(long oldConstraints, long newConstraints, int skipCount, int threshold);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyAttributeScope extends androidx.constraintlayout.compose.BaseKeyFrameScope {
+ method public float getAlpha();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getRotationZ();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public void setAlpha(float);
+ method public void setRotationX(float);
+ method public void setRotationY(float);
+ method public void setRotationZ(float);
+ method public void setScaleX(float);
+ method public void setScaleY(float);
+ method public void setTranslationX(float);
+ method public void setTranslationY(float);
+ method public void setTranslationZ(float);
+ property public final float alpha;
+ property public final float rotationX;
+ property public final float rotationY;
+ property public final float rotationZ;
+ property public final float scaleX;
+ property public final float scaleY;
+ property public final float translationX;
+ property public final float translationY;
+ property public final float translationZ;
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyAttributesScope extends androidx.constraintlayout.compose.BaseKeyFramesScope {
+ method public void frame(@IntRange(from=0L, to=100L) int frame, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyAttributeScope,kotlin.Unit> keyFrameContent);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyCycleScope extends androidx.constraintlayout.compose.BaseKeyFrameScope {
+ method public float getAlpha();
+ method public float getOffset();
+ method public float getPeriod();
+ method public float getPhase();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getRotationZ();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public void setAlpha(float);
+ method public void setOffset(float);
+ method public void setPeriod(float);
+ method public void setPhase(float);
+ method public void setRotationX(float);
+ method public void setRotationY(float);
+ method public void setRotationZ(float);
+ method public void setScaleX(float);
+ method public void setScaleY(float);
+ method public void setTranslationX(float);
+ method public void setTranslationY(float);
+ method public void setTranslationZ(float);
+ property public final float alpha;
+ property public final float offset;
+ property public final float period;
+ property public final float phase;
+ property public final float rotationX;
+ property public final float rotationY;
+ property public final float rotationZ;
+ property public final float scaleX;
+ property public final float scaleY;
+ property public final float translationX;
+ property public final float translationY;
+ property public final float translationZ;
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyCyclesScope extends androidx.constraintlayout.compose.BaseKeyFramesScope {
+ method public void frame(@IntRange(from=0L, to=100L) int frame, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyCycleScope,kotlin.Unit> keyFrameContent);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyPositionScope extends androidx.constraintlayout.compose.BaseKeyFrameScope {
+ method public androidx.constraintlayout.compose.CurveFit? getCurveFit();
+ method public float getPercentHeight();
+ method public float getPercentWidth();
+ method public float getPercentX();
+ method public float getPercentY();
+ method public void setCurveFit(androidx.constraintlayout.compose.CurveFit?);
+ method public void setPercentHeight(float);
+ method public void setPercentWidth(float);
+ method public void setPercentX(float);
+ method public void setPercentY(float);
+ property public final androidx.constraintlayout.compose.CurveFit? curveFit;
+ property public final float percentHeight;
+ property public final float percentWidth;
+ property public final float percentX;
+ property public final float percentY;
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class KeyPositionsScope extends androidx.constraintlayout.compose.BaseKeyFramesScope {
+ method public void frame(@IntRange(from=0L, to=100L) int frame, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyPositionScope,kotlin.Unit> keyFrameContent);
+ method public androidx.constraintlayout.compose.RelativePosition getType();
+ method public void setType(androidx.constraintlayout.compose.RelativePosition);
+ property public final androidx.constraintlayout.compose.RelativePosition type;
+ }
+
+ public final class LateMotionLayoutKt {
+ method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static void LateMotionLayout(androidx.compose.runtime.MutableState<androidx.constraintlayout.compose.ConstraintSet?> start, androidx.compose.runtime.MutableState<androidx.constraintlayout.compose.ConstraintSet?> end, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlinx.coroutines.channels.Channel<androidx.constraintlayout.compose.ConstraintSet> channel, androidx.compose.runtime.State<kotlin.Unit> contentTracker, androidx.compose.ui.node.Ref<androidx.constraintlayout.compose.CompositionSource> compositionSource, int optimizationLevel, kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ public enum LayoutInfoFlags {
+ enum_constant public static final androidx.constraintlayout.compose.LayoutInfoFlags BOUNDS;
+ enum_constant public static final androidx.constraintlayout.compose.LayoutInfoFlags NONE;
+ }
+
+ public interface LayoutInformationReceiver {
+ method public androidx.constraintlayout.compose.MotionLayoutDebugFlags getForcedDrawDebug();
+ method public int getForcedHeight();
+ method public float getForcedProgress();
+ method public int getForcedWidth();
+ method public androidx.constraintlayout.compose.LayoutInfoFlags getLayoutInformationMode();
+ method public void onNewProgress(float progress);
+ method public void resetForcedProgress();
+ method public void setLayoutInformation(String information);
+ method public void setUpdateFlag(androidx.compose.runtime.MutableState<java.lang.Long> needsUpdate);
+ }
+
+ @androidx.compose.runtime.Stable public abstract class LayoutReference {
+ }
+
+ @kotlin.PublishedApi internal class Measurer implements androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer androidx.constraintlayout.compose.DesignInfoProvider {
+ ctor public Measurer(androidx.compose.ui.unit.Density density);
+ method public final void addLayoutInformationReceiver(androidx.constraintlayout.compose.LayoutInformationReceiver? layoutReceiver);
+ method protected final void applyRootSize(long constraints);
+ method public void computeLayoutResult();
+ method @androidx.compose.runtime.Composable public final void createDesignElements();
+ method public void didMeasures();
+ method @androidx.compose.runtime.Composable public final void drawDebugBounds(androidx.compose.foundation.layout.BoxScope, float forcedScaleFactor);
+ method public final void drawDebugBounds(androidx.compose.ui.graphics.drawscope.DrawScope, float forcedScaleFactor);
+ method public String getDesignInfo(int startX, int startY, String args);
+ method public final float getForcedScaleFactor();
+ method protected final java.util.Map<androidx.compose.ui.layout.Measurable,androidx.constraintlayout.core.state.WidgetFrame> getFrameCache();
+ method public final int getLayoutCurrentHeight();
+ method public final int getLayoutCurrentWidth();
+ method protected final androidx.constraintlayout.compose.LayoutInformationReceiver? getLayoutInformationReceiver();
+ method protected final java.util.Map<androidx.compose.ui.layout.Measurable,androidx.compose.ui.layout.Placeable> getPlaceables();
+ method protected final androidx.constraintlayout.core.widgets.ConstraintWidgetContainer getRoot();
+ method protected final androidx.constraintlayout.compose.State getState();
+ method public void measure(androidx.constraintlayout.core.widgets.ConstraintWidget constraintWidget, androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measure measure);
+ method public final void parseDesignElements(androidx.constraintlayout.compose.ConstraintSet constraintSet);
+ method public final void performLayout(androidx.compose.ui.layout.Placeable.PlacementScope, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables);
+ method public final long performMeasure(long constraints, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.constraintlayout.compose.ConstraintSet constraintSet, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables, int optimizationLevel);
+ method public final void setForcedScaleFactor(float);
+ method protected final void setLayoutInformationReceiver(androidx.constraintlayout.compose.LayoutInformationReceiver?);
+ property public final float forcedScaleFactor;
+ property protected final java.util.Map<androidx.compose.ui.layout.Measurable,androidx.constraintlayout.core.state.WidgetFrame> frameCache;
+ property public final int layoutCurrentHeight;
+ property public final int layoutCurrentWidth;
+ property protected final androidx.constraintlayout.compose.LayoutInformationReceiver? layoutInformationReceiver;
+ property protected final java.util.Map<androidx.compose.ui.layout.Measurable,androidx.compose.ui.layout.Placeable> placeables;
+ property protected final androidx.constraintlayout.core.widgets.ConstraintWidgetContainer root;
+ property protected final androidx.constraintlayout.compose.State state;
+ }
+
+ public final class MotionCarouselKt {
+ method @androidx.compose.runtime.Composable public static void ItemHolder(int i, String slotPrefix, boolean showSlot, kotlin.jvm.functions.Function0<kotlin.Unit> function);
+ method @androidx.compose.runtime.Composable public static void MotionCarousel(androidx.constraintlayout.compose.MotionScene motionScene, int initialSlotIndex, int numSlots, optional String backwardTransition, optional String forwardTransition, optional String slotPrefix, optional boolean showSlots, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionCarouselScope,kotlin.Unit> content);
+ method public static inline <T> void items(androidx.constraintlayout.compose.MotionCarouselScope, java.util.List<? extends T> items, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> itemContent);
+ method public static inline <T> void itemsWithProperties(androidx.constraintlayout.compose.MotionCarouselScope, java.util.List<? extends T> items, kotlin.jvm.functions.Function2<? super T,? super androidx.compose.runtime.State<androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties>,kotlin.Unit> itemContent);
+ }
+
+ public interface MotionCarouselScope {
+ method public void items(int count, kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> itemContent);
+ method public void itemsWithProperties(int count, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super androidx.compose.runtime.State<androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties>,kotlin.Unit> itemContent);
+ }
+
+ public interface MotionItemsProvider {
+ method public int count();
+ method public kotlin.jvm.functions.Function0<kotlin.Unit> getContent(int index);
+ method public kotlin.jvm.functions.Function0<kotlin.Unit> getContent(int index, androidx.compose.runtime.State<androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties> properties);
+ method public boolean hasItemsWithProperties();
+ }
+
+ public enum MotionLayoutDebugFlags {
+ enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags NONE;
+ enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags SHOW_ALL;
+ enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags UNKNOWN;
+ }
+
+ @Deprecated public enum MotionLayoutFlag {
+ enum_constant @Deprecated public static final androidx.constraintlayout.compose.MotionLayoutFlag Default;
+ enum_constant @Deprecated public static final androidx.constraintlayout.compose.MotionLayoutFlag FullMeasure;
+ }
+
+ public final class MotionLayoutKt {
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static inline void MotionLayout(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, float progress, optional androidx.compose.ui.Modifier modifier, optional androidx.constraintlayout.compose.Transition? transition, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, float progress, optional androidx.compose.ui.Modifier modifier, optional String transitionName, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, String? constraintSetName, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi @kotlin.PublishedApi internal static void MotionLayoutCore(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, androidx.constraintlayout.compose.Transition? transition, float progress, androidx.constraintlayout.compose.LayoutInformationReceiver? informationReceiver, int optimizationLevel, boolean showBounds, boolean showPaths, boolean showKeyPositions, androidx.compose.ui.Modifier modifier, androidx.compose.runtime.MutableState<kotlin.Unit> contentTracker, androidx.compose.ui.node.Ref<androidx.constraintlayout.compose.CompositionSource> compositionSource, androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi @kotlin.PublishedApi internal static void MotionLayoutCore(androidx.constraintlayout.compose.MotionScene motionScene, float progress, String transitionName, int optimizationLevel, int debugFlags, androidx.compose.ui.Modifier modifier, androidx.compose.runtime.MutableState<kotlin.Unit> contentTracker, androidx.compose.ui.node.Ref<androidx.constraintlayout.compose.CompositionSource> compositionSource, androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi @kotlin.PublishedApi internal static void MotionLayoutCore(androidx.constraintlayout.compose.MotionScene motionScene, String? constraintSetName, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, optional int debugFlags, optional int optimizationLevel, androidx.compose.runtime.MutableState<kotlin.Unit> contentTracker, androidx.compose.ui.node.Ref<androidx.constraintlayout.compose.CompositionSource> compositionSource, androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class MotionLayoutScope {
+ method public long customColor(String id, String name);
+ method public float customDistance(String id, String name);
+ method public float customFloat(String id, String name);
+ method public long customFontSize(String id, String name);
+ method public int customInt(String id, String name);
+ method public androidx.constraintlayout.compose.MotionLayoutScope.CustomProperties customProperties(String id);
+ method @Deprecated public long motionColor(String id, String name);
+ method @Deprecated public float motionDistance(String id, String name);
+ method @Deprecated public float motionFloat(String id, String name);
+ method @Deprecated public long motionFontSize(String id, String name);
+ method @Deprecated public int motionInt(String id, String name);
+ method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties> motionProperties(String id);
+ method @Deprecated public androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties motionProperties(String id, String tag);
+ method public androidx.compose.ui.Modifier onStartEndBoundsChanged(androidx.compose.ui.Modifier, Object layoutId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.geometry.Rect,? super androidx.compose.ui.geometry.Rect,kotlin.Unit> onBoundsChanged);
+ }
+
+ public final class MotionLayoutScope.CustomProperties {
+ method public long color(String name);
+ method public float distance(String name);
+ method public float float(String name);
+ method public long fontSize(String name);
+ method public int int(String name);
+ }
+
+ public final class MotionLayoutScope.MotionProperties {
+ method public long color(String name);
+ method public float distance(String name);
+ method public float float(String name);
+ method public long fontSize(String name);
+ method public String id();
+ method public int int(String name);
+ method public String? tag();
+ }
+
+ @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.constraintlayout.compose.ExperimentalMotionApi public interface MotionScene extends androidx.constraintlayout.core.state.CoreMotionScene {
+ method public androidx.constraintlayout.compose.ConstraintSet? getConstraintSetInstance(String name);
+ method public androidx.constraintlayout.compose.Transition? getTransitionInstance(String name);
+ }
+
+ public final class MotionSceneKt {
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static androidx.constraintlayout.compose.MotionScene MotionScene(@org.intellij.lang.annotations.Language("json5") String content);
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class MotionSceneScope {
+ method public androidx.constraintlayout.compose.ConstraintSetRef addConstraintSet(androidx.constraintlayout.compose.ConstraintSet constraintSet, optional String? name);
+ method public void addTransition(androidx.constraintlayout.compose.Transition transition, optional String? name);
+ method public androidx.constraintlayout.compose.ConstraintSetRef constraintSet(optional String? name, optional androidx.constraintlayout.compose.ConstraintSetRef? extendConstraintSet, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstraintSetScope,kotlin.Unit> constraintSetContent);
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRefFor(Object id);
+ method public androidx.constraintlayout.compose.MotionSceneScope.ConstrainedLayoutReferences createRefsFor(java.lang.Object... ids);
+ method public void customColor(androidx.constraintlayout.compose.ConstrainScope, String name, long value);
+ method public void customColor(androidx.constraintlayout.compose.KeyAttributeScope, String name, long value);
+ method public void customDistance(androidx.constraintlayout.compose.ConstrainScope, String name, float value);
+ method public void customDistance(androidx.constraintlayout.compose.KeyAttributeScope, String name, float value);
+ method public void customFloat(androidx.constraintlayout.compose.ConstrainScope, String name, float value);
+ method public void customFloat(androidx.constraintlayout.compose.KeyAttributeScope, String name, float value);
+ method public void customFontSize(androidx.constraintlayout.compose.ConstrainScope, String name, long value);
+ method public void customFontSize(androidx.constraintlayout.compose.KeyAttributeScope, String name, long value);
+ method public void customInt(androidx.constraintlayout.compose.ConstrainScope, String name, int value);
+ method public void customInt(androidx.constraintlayout.compose.KeyAttributeScope, String name, int value);
+ method public void defaultTransition(androidx.constraintlayout.compose.ConstraintSetRef from, androidx.constraintlayout.compose.ConstraintSetRef to, optional kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.TransitionScope,kotlin.Unit> transitionContent);
+ method public float getStaggeredWeight(androidx.constraintlayout.compose.ConstrainScope);
+ method public void setStaggeredWeight(androidx.constraintlayout.compose.ConstrainScope, float);
+ method public void transition(androidx.constraintlayout.compose.ConstraintSetRef from, androidx.constraintlayout.compose.ConstraintSetRef to, optional String? name, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.TransitionScope,kotlin.Unit> transitionContent);
+ }
+
+ public final class MotionSceneScope.ConstrainedLayoutReferences {
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component1();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component10();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component11();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component12();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component13();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component14();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component15();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component16();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component2();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component3();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component4();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component5();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component6();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component7();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component8();
+ method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component9();
+ }
+
+ public final class MotionSceneScopeKt {
+ method @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public static androidx.constraintlayout.compose.MotionScene MotionScene(kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionSceneScope,kotlin.Unit> motionSceneContent);
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class OnSwipe {
+ ctor public OnSwipe(androidx.constraintlayout.compose.ConstrainedLayoutReference anchor, androidx.constraintlayout.compose.SwipeSide side, androidx.constraintlayout.compose.SwipeDirection direction, optional float dragScale, optional float dragThreshold, optional androidx.constraintlayout.compose.ConstrainedLayoutReference? dragAround, optional androidx.constraintlayout.compose.ConstrainedLayoutReference? limitBoundsTo, optional androidx.constraintlayout.compose.SwipeTouchUp onTouchUp, optional androidx.constraintlayout.compose.SwipeMode mode);
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference getAnchor();
+ method public androidx.constraintlayout.compose.SwipeDirection getDirection();
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference? getDragAround();
+ method public float getDragScale();
+ method public float getDragThreshold();
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference? getLimitBoundsTo();
+ method public androidx.constraintlayout.compose.SwipeMode getMode();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getOnTouchUp();
+ method public androidx.constraintlayout.compose.SwipeSide getSide();
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference anchor;
+ property public final androidx.constraintlayout.compose.SwipeDirection direction;
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference? dragAround;
+ property public final float dragScale;
+ property public final float dragThreshold;
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference? limitBoundsTo;
+ property public final androidx.constraintlayout.compose.SwipeMode mode;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp onTouchUp;
+ property public final androidx.constraintlayout.compose.SwipeSide side;
+ }
+
+ @androidx.compose.runtime.Immutable @kotlin.PublishedApi internal final class RawConstraintSet implements androidx.constraintlayout.compose.ConstraintSet {
+ ctor public RawConstraintSet(androidx.constraintlayout.core.parser.CLObject clObject);
+ method public void applyTo(androidx.constraintlayout.compose.State state, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables);
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class RelativePosition {
+ method public String getName();
+ property public String name;
+ field public static final androidx.constraintlayout.compose.RelativePosition.Companion Companion;
+ }
+
+ public static final class RelativePosition.Companion {
+ method public androidx.constraintlayout.compose.RelativePosition getDelta();
+ method public androidx.constraintlayout.compose.RelativePosition getParent();
+ method public androidx.constraintlayout.compose.RelativePosition getPath();
+ property public final androidx.constraintlayout.compose.RelativePosition Delta;
+ property public final androidx.constraintlayout.compose.RelativePosition Parent;
+ property public final androidx.constraintlayout.compose.RelativePosition Path;
+ }
+
+ @kotlin.jvm.JvmInline public final value class Skip {
+ ctor public Skip(@IntRange(from=0L) int position, @IntRange(from=1L) int size);
+ ctor public Skip(@IntRange(from=0L) int position, @IntRange(from=1L) int rows, @IntRange(from=1L) int columns);
+ method public String getDescription();
+ property public final String description;
+ }
+
+ @kotlin.jvm.JvmInline public final value class Span {
+ ctor public Span(@IntRange(from=0L) int position, @IntRange(from=1L) int size);
+ ctor public Span(@IntRange(from=0L) int position, @IntRange(from=1L) int rows, @IntRange(from=1L) int columns);
+ ctor public Span(String description);
+ method public String getDescription();
+ property public final String description;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class SpringBoundary {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.SpringBoundary.Companion Companion;
+ }
+
+ public static final class SpringBoundary.Companion {
+ method public androidx.constraintlayout.compose.SpringBoundary getBounceBoth();
+ method public androidx.constraintlayout.compose.SpringBoundary getBounceEnd();
+ method public androidx.constraintlayout.compose.SpringBoundary getBounceStart();
+ method public androidx.constraintlayout.compose.SpringBoundary getOvershoot();
+ property public final androidx.constraintlayout.compose.SpringBoundary BounceBoth;
+ property public final androidx.constraintlayout.compose.SpringBoundary BounceEnd;
+ property public final androidx.constraintlayout.compose.SpringBoundary BounceStart;
+ property public final androidx.constraintlayout.compose.SpringBoundary Overshoot;
+ }
+
+ public final class State extends androidx.constraintlayout.core.state.State {
+ ctor public State(androidx.compose.ui.unit.Density density);
+ method public androidx.compose.ui.unit.Density getDensity();
+ method @Deprecated public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
+ method public long getRootIncomingConstraints();
+ method @Deprecated public void setLayoutDirection(androidx.compose.ui.unit.LayoutDirection);
+ method public void setRootIncomingConstraints(long);
+ property public final androidx.compose.ui.unit.Density density;
+ property @Deprecated public final androidx.compose.ui.unit.LayoutDirection layoutDirection;
+ property public final long rootIncomingConstraints;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class SwipeDirection {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.SwipeDirection.Companion Companion;
+ }
+
+ public static final class SwipeDirection.Companion {
+ method public androidx.constraintlayout.compose.SwipeDirection getClockwise();
+ method public androidx.constraintlayout.compose.SwipeDirection getCounterclockwise();
+ method public androidx.constraintlayout.compose.SwipeDirection getDown();
+ method public androidx.constraintlayout.compose.SwipeDirection getEnd();
+ method public androidx.constraintlayout.compose.SwipeDirection getLeft();
+ method public androidx.constraintlayout.compose.SwipeDirection getRight();
+ method public androidx.constraintlayout.compose.SwipeDirection getStart();
+ method public androidx.constraintlayout.compose.SwipeDirection getUp();
+ property public final androidx.constraintlayout.compose.SwipeDirection Clockwise;
+ property public final androidx.constraintlayout.compose.SwipeDirection Counterclockwise;
+ property public final androidx.constraintlayout.compose.SwipeDirection Down;
+ property public final androidx.constraintlayout.compose.SwipeDirection End;
+ property public final androidx.constraintlayout.compose.SwipeDirection Left;
+ property public final androidx.constraintlayout.compose.SwipeDirection Right;
+ property public final androidx.constraintlayout.compose.SwipeDirection Start;
+ property public final androidx.constraintlayout.compose.SwipeDirection Up;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class SwipeMode {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.SwipeMode.Companion Companion;
+ }
+
+ public static final class SwipeMode.Companion {
+ method public androidx.constraintlayout.compose.SwipeMode getSpring();
+ method public androidx.constraintlayout.compose.SwipeMode getVelocity();
+ method public androidx.constraintlayout.compose.SwipeMode spring(optional float mass, optional float stiffness, optional float damping, optional float threshold, optional androidx.constraintlayout.compose.SpringBoundary boundary);
+ method public androidx.constraintlayout.compose.SwipeMode velocity(optional float maxVelocity, optional float maxAcceleration);
+ property public final androidx.constraintlayout.compose.SwipeMode Spring;
+ property public final androidx.constraintlayout.compose.SwipeMode Velocity;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class SwipeSide {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.SwipeSide.Companion Companion;
+ }
+
+ public static final class SwipeSide.Companion {
+ method public androidx.constraintlayout.compose.SwipeSide getBottom();
+ method public androidx.constraintlayout.compose.SwipeSide getEnd();
+ method public androidx.constraintlayout.compose.SwipeSide getLeft();
+ method public androidx.constraintlayout.compose.SwipeSide getMiddle();
+ method public androidx.constraintlayout.compose.SwipeSide getRight();
+ method public androidx.constraintlayout.compose.SwipeSide getStart();
+ method public androidx.constraintlayout.compose.SwipeSide getTop();
+ property public final androidx.constraintlayout.compose.SwipeSide Bottom;
+ property public final androidx.constraintlayout.compose.SwipeSide End;
+ property public final androidx.constraintlayout.compose.SwipeSide Left;
+ property public final androidx.constraintlayout.compose.SwipeSide Middle;
+ property public final androidx.constraintlayout.compose.SwipeSide Right;
+ property public final androidx.constraintlayout.compose.SwipeSide Start;
+ property public final androidx.constraintlayout.compose.SwipeSide Top;
+ }
+
+ @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public final class SwipeTouchUp {
+ method public String getName();
+ property public final String name;
+ field public static final androidx.constraintlayout.compose.SwipeTouchUp.Companion Companion;
+ }
+
+ public static final class SwipeTouchUp.Companion {
+ method public androidx.constraintlayout.compose.SwipeTouchUp getAutoComplete();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getDecelerate();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getNeverCompleteEnd();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getNeverCompleteStart();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getStop();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getToEnd();
+ method public androidx.constraintlayout.compose.SwipeTouchUp getToStart();
+ property public final androidx.constraintlayout.compose.SwipeTouchUp AutoComplete;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp Decelerate;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp NeverCompleteEnd;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp NeverCompleteStart;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp Stop;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp ToEnd;
+ property public final androidx.constraintlayout.compose.SwipeTouchUp ToStart;
+ }
+
+ public final class ToolingUtilsKt {
+ method public static androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.constraintlayout.compose.DesignInfoProvider> getDesignInfoDataKey();
+ property public static final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.constraintlayout.compose.DesignInfoProvider> DesignInfoDataKey;
+ field @kotlin.PublishedApi internal static final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.constraintlayout.compose.DesignInfoProvider> designInfoProvider$delegate;
+ }
+
+ @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.constraintlayout.compose.ExperimentalMotionApi public interface Transition {
+ method public String getEndConstraintSetId();
+ method public String getStartConstraintSetId();
+ }
+
+ public final class TransitionKt {
+ method @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public static androidx.constraintlayout.compose.Transition Transition(@org.intellij.lang.annotations.Language("json5") String content);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class TransitionScope {
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRefFor(Object id);
+ method public float getMaxStaggerDelay();
+ method public androidx.constraintlayout.compose.Arc getMotionArc();
+ method public androidx.constraintlayout.compose.OnSwipe? getOnSwipe();
+ method public void keyAttributes(androidx.constraintlayout.compose.ConstrainedLayoutReference[] targets, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyAttributesScope,kotlin.Unit> keyAttributesContent);
+ method public void keyCycles(androidx.constraintlayout.compose.ConstrainedLayoutReference[] targets, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyCyclesScope,kotlin.Unit> keyCyclesContent);
+ method public void keyPositions(androidx.constraintlayout.compose.ConstrainedLayoutReference[] targets, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.KeyPositionsScope,kotlin.Unit> keyPositionsContent);
+ method public void setMaxStaggerDelay(float);
+ method public void setMotionArc(androidx.constraintlayout.compose.Arc);
+ method public void setOnSwipe(androidx.constraintlayout.compose.OnSwipe?);
+ property public final float maxStaggerDelay;
+ property public final androidx.constraintlayout.compose.Arc motionArc;
+ property public final androidx.constraintlayout.compose.OnSwipe? onSwipe;
+ }
+
+ public final class TransitionScopeKt {
+ method @SuppressCompatibility @androidx.constraintlayout.compose.ExperimentalMotionApi public static androidx.constraintlayout.compose.Transition Transition(optional String from, optional String to, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.TransitionScope,kotlin.Unit> content);
+ }
+
+ @androidx.compose.runtime.Immutable public final class VerticalAlign {
+ field public static final androidx.constraintlayout.compose.VerticalAlign.Companion Companion;
+ }
+
+ public static final class VerticalAlign.Companion {
+ method public androidx.constraintlayout.compose.VerticalAlign getBaseline();
+ method public androidx.constraintlayout.compose.VerticalAlign getBottom();
+ method public androidx.constraintlayout.compose.VerticalAlign getCenter();
+ method public androidx.constraintlayout.compose.VerticalAlign getTop();
+ property public final androidx.constraintlayout.compose.VerticalAlign Baseline;
+ property public final androidx.constraintlayout.compose.VerticalAlign Bottom;
+ property public final androidx.constraintlayout.compose.VerticalAlign Center;
+ property public final androidx.constraintlayout.compose.VerticalAlign Top;
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface VerticalAnchorable {
+ method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor anchor, optional float margin, optional float goneMargin);
+ }
+
+ @androidx.compose.runtime.Stable public final class VerticalChainReference extends androidx.constraintlayout.compose.LayoutReference {
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor getBottom();
+ method public androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor getTop();
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor bottom;
+ property public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor top;
+ }
+
+ @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Stable public final class VerticalChainScope {
+ method public androidx.constraintlayout.compose.HorizontalAnchorable getBottom();
+ method public androidx.constraintlayout.compose.ConstrainedLayoutReference getParent();
+ method public androidx.constraintlayout.compose.HorizontalAnchorable getTop();
+ property public final androidx.constraintlayout.compose.HorizontalAnchorable bottom;
+ property public final androidx.constraintlayout.compose.ConstrainedLayoutReference parent;
+ property public final androidx.constraintlayout.compose.HorizontalAnchorable top;
+ }
+
+ @androidx.compose.runtime.Immutable public final class Visibility {
+ field public static final androidx.constraintlayout.compose.Visibility.Companion Companion;
+ }
+
+ public static final class Visibility.Companion {
+ method public androidx.constraintlayout.compose.Visibility getGone();
+ method public androidx.constraintlayout.compose.Visibility getInvisible();
+ method public androidx.constraintlayout.compose.Visibility getVisible();
+ property public final androidx.constraintlayout.compose.Visibility Gone;
+ property public final androidx.constraintlayout.compose.Visibility Invisible;
+ property public final androidx.constraintlayout.compose.Visibility Visible;
+ }
+
+ @androidx.compose.runtime.Immutable public final class Wrap {
+ field public static final androidx.constraintlayout.compose.Wrap.Companion Companion;
+ }
+
+ public static final class Wrap.Companion {
+ method public androidx.constraintlayout.compose.Wrap getAligned();
+ method public androidx.constraintlayout.compose.Wrap getChain();
+ method public androidx.constraintlayout.compose.Wrap getNone();
+ property public final androidx.constraintlayout.compose.Wrap Aligned;
+ property public final androidx.constraintlayout.compose.Wrap Chain;
+ property public final androidx.constraintlayout.compose.Wrap None;
+ }
+
+}
+
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/MotionLayoutBenchmark.kt b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/MotionLayoutBenchmark.kt
index daa4e5d..cf8d5c8f 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/MotionLayoutBenchmark.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/MotionLayoutBenchmark.kt
@@ -121,7 +121,7 @@
packageName = PACKAGE_NAME,
metrics = listOf(FrameTimingMetric()),
compilationMode = CompilationMode.DEFAULT,
- iterations = 10,
+ iterations = 8,
// HOT causes issues with the measure block logic where multiple click actions are
// triggered at once
startupMode = StartupMode.WARM,
diff --git a/constraintlayout/constraintlayout-core/api/1.1.0-beta01.txt b/constraintlayout/constraintlayout-core/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..5be1d12
--- /dev/null
+++ b/constraintlayout/constraintlayout-core/api/1.1.0-beta01.txt
@@ -0,0 +1,3393 @@
+// Signature format: 4.0
+package androidx.constraintlayout.core {
+
+ public class ArrayLinkedVariables implements androidx.constraintlayout.core.ArrayRow.ArrayRowVariables {
+ method public void add(androidx.constraintlayout.core.SolverVariable!, float, boolean);
+ method public final void clear();
+ method public boolean contains(androidx.constraintlayout.core.SolverVariable!);
+ method public void display();
+ method public void divideByAmount(float);
+ method public final float get(androidx.constraintlayout.core.SolverVariable!);
+ method public int getCurrentSize();
+ method public int getHead();
+ method public final int getId(int);
+ method public final int getNextIndice(int);
+ method public final float getValue(int);
+ method public androidx.constraintlayout.core.SolverVariable! getVariable(int);
+ method public float getVariableValue(int);
+ method public int indexOf(androidx.constraintlayout.core.SolverVariable!);
+ method public void invert();
+ method public final void put(androidx.constraintlayout.core.SolverVariable!, float);
+ method public final float remove(androidx.constraintlayout.core.SolverVariable!, boolean);
+ method public int sizeInBytes();
+ method public float use(androidx.constraintlayout.core.ArrayRow!, boolean);
+ field protected final androidx.constraintlayout.core.Cache! mCache;
+ }
+
+ public class ArrayRow {
+ ctor public ArrayRow();
+ ctor public ArrayRow(androidx.constraintlayout.core.Cache!);
+ method public androidx.constraintlayout.core.ArrayRow! addError(androidx.constraintlayout.core.LinearSystem!, int);
+ method public void addError(androidx.constraintlayout.core.SolverVariable!);
+ method public void clear();
+ method public androidx.constraintlayout.core.ArrayRow! createRowDimensionRatio(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, float);
+ method public androidx.constraintlayout.core.ArrayRow! createRowEqualDimension(float, float, float, androidx.constraintlayout.core.SolverVariable!, int, androidx.constraintlayout.core.SolverVariable!, int, androidx.constraintlayout.core.SolverVariable!, int, androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.ArrayRow! createRowEqualMatchDimensions(float, float, float, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!);
+ method public androidx.constraintlayout.core.ArrayRow! createRowEquals(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.ArrayRow! createRowEquals(androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.ArrayRow! createRowGreaterThan(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.ArrayRow! createRowGreaterThan(androidx.constraintlayout.core.SolverVariable!, int, androidx.constraintlayout.core.SolverVariable!);
+ method public androidx.constraintlayout.core.ArrayRow! createRowLowerThan(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.ArrayRow! createRowWithAngle(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, float);
+ method public androidx.constraintlayout.core.SolverVariable! getKey();
+ method public androidx.constraintlayout.core.SolverVariable! getPivotCandidate(androidx.constraintlayout.core.LinearSystem!, boolean[]!);
+ method public void initFromRow(androidx.constraintlayout.core.LinearSystem.Row!);
+ method public boolean isEmpty();
+ method public androidx.constraintlayout.core.SolverVariable! pickPivot(androidx.constraintlayout.core.SolverVariable!);
+ method public void reset();
+ method public void updateFromFinalVariable(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.SolverVariable!, boolean);
+ method public void updateFromRow(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.ArrayRow!, boolean);
+ method public void updateFromSynonymVariable(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.SolverVariable!, boolean);
+ method public void updateFromSystem(androidx.constraintlayout.core.LinearSystem!);
+ field public androidx.constraintlayout.core.ArrayRow.ArrayRowVariables! variables;
+ }
+
+ public static interface ArrayRow.ArrayRowVariables {
+ method public void add(androidx.constraintlayout.core.SolverVariable!, float, boolean);
+ method public void clear();
+ method public boolean contains(androidx.constraintlayout.core.SolverVariable!);
+ method public void display();
+ method public void divideByAmount(float);
+ method public float get(androidx.constraintlayout.core.SolverVariable!);
+ method public int getCurrentSize();
+ method public androidx.constraintlayout.core.SolverVariable! getVariable(int);
+ method public float getVariableValue(int);
+ method public int indexOf(androidx.constraintlayout.core.SolverVariable!);
+ method public void invert();
+ method public void put(androidx.constraintlayout.core.SolverVariable!, float);
+ method public float remove(androidx.constraintlayout.core.SolverVariable!, boolean);
+ method public int sizeInBytes();
+ method public float use(androidx.constraintlayout.core.ArrayRow!, boolean);
+ }
+
+ public class Cache {
+ ctor public Cache();
+ }
+
+ public class GoalRow extends androidx.constraintlayout.core.ArrayRow {
+ ctor public GoalRow(androidx.constraintlayout.core.Cache!);
+ }
+
+ public class LinearSystem {
+ ctor public LinearSystem();
+ method public void addCenterPoint(androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintWidget!, float, int);
+ method public void addCentering(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, float, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, int);
+ method public void addConstraint(androidx.constraintlayout.core.ArrayRow!);
+ method public androidx.constraintlayout.core.ArrayRow! addEquality(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, int);
+ method public void addEquality(androidx.constraintlayout.core.SolverVariable!, int);
+ method public void addGreaterBarrier(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, boolean);
+ method public void addGreaterThan(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, int);
+ method public void addLowerBarrier(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, boolean);
+ method public void addLowerThan(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, int);
+ method public void addRatio(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, float, int);
+ method public void addSynonym(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.SolverVariable! createErrorVariable(int, String!);
+ method public androidx.constraintlayout.core.SolverVariable! createExtraVariable();
+ method public androidx.constraintlayout.core.SolverVariable! createObjectVariable(Object!);
+ method public androidx.constraintlayout.core.ArrayRow! createRow();
+ method public static androidx.constraintlayout.core.ArrayRow! createRowDimensionPercent(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, float);
+ method public androidx.constraintlayout.core.SolverVariable! createSlackVariable();
+ method public void displayReadableRows();
+ method public void displayVariablesReadableRows();
+ method public void fillMetrics(androidx.constraintlayout.core.Metrics!);
+ method public androidx.constraintlayout.core.Cache! getCache();
+ method public int getMemoryUsed();
+ method public static androidx.constraintlayout.core.Metrics! getMetrics();
+ method public int getNumEquations();
+ method public int getNumVariables();
+ method public int getObjectVariableValue(Object!);
+ method public void minimize() throws java.lang.Exception;
+ method public void removeRow(androidx.constraintlayout.core.ArrayRow!);
+ method public void reset();
+ field public static long ARRAY_ROW_CREATION;
+ field public static final boolean DEBUG = false;
+ field public static final boolean FULL_DEBUG = false;
+ field public static long OPTIMIZED_ARRAY_ROW_CREATION;
+ field public static boolean OPTIMIZED_ENGINE;
+ field public static boolean SIMPLIFY_SYNONYMS;
+ field public static boolean SKIP_COLUMNS;
+ field public static boolean USE_BASIC_SYNONYMS;
+ field public static boolean USE_DEPENDENCY_ORDERING;
+ field public static boolean USE_SYNONYMS;
+ field public boolean graphOptimizer;
+ field public boolean hasSimpleDefinition;
+ field public boolean newgraphOptimizer;
+ field public static androidx.constraintlayout.core.Metrics! sMetrics;
+ }
+
+ public class Metrics {
+ ctor public Metrics();
+ method public void copy(androidx.constraintlayout.core.Metrics!);
+ method public void reset();
+ field public long additionalMeasures;
+ field public long bfs;
+ field public long constraints;
+ field public long determineGroups;
+ field public long errors;
+ field public long extravariables;
+ field public long fullySolved;
+ field public long graphOptimizer;
+ field public long graphSolved;
+ field public long grouping;
+ field public long infeasibleDetermineGroups;
+ field public long iterations;
+ field public long lastTableSize;
+ field public long layouts;
+ field public long linearSolved;
+ field public long mChildCount;
+ field public long mEquations;
+ field public long mMeasureCalls;
+ field public long mMeasureDuration;
+ field public int mNumberOfLayouts;
+ field public int mNumberOfMeasures;
+ field public long mSimpleEquations;
+ field public long mSolverPasses;
+ field public long mVariables;
+ field public long maxRows;
+ field public long maxTableSize;
+ field public long maxVariables;
+ field public long measuredMatchWidgets;
+ field public long measuredWidgets;
+ field public long measures;
+ field public long measuresLayoutDuration;
+ field public long measuresWidgetsDuration;
+ field public long measuresWrap;
+ field public long measuresWrapInfeasible;
+ field public long minimize;
+ field public long minimizeGoal;
+ field public long nonresolvedWidgets;
+ field public long optimize;
+ field public long pivots;
+ field public java.util.ArrayList<java.lang.String!>! problematicLayouts;
+ field public long resolutions;
+ field public long resolvedWidgets;
+ field public long simpleconstraints;
+ field public long slackvariables;
+ field public long tableSizeIncrease;
+ field public long variables;
+ field public long widgets;
+ }
+
+ public class PriorityGoalRow extends androidx.constraintlayout.core.ArrayRow {
+ ctor public PriorityGoalRow(androidx.constraintlayout.core.Cache!);
+ }
+
+ public class SolverVariable implements java.lang.Comparable<androidx.constraintlayout.core.SolverVariable!> {
+ ctor public SolverVariable(androidx.constraintlayout.core.SolverVariable.Type!, String!);
+ ctor public SolverVariable(String!, androidx.constraintlayout.core.SolverVariable.Type!);
+ method public final void addToRow(androidx.constraintlayout.core.ArrayRow!);
+ method public int compareTo(androidx.constraintlayout.core.SolverVariable!);
+ method public String! getName();
+ method public final void removeFromRow(androidx.constraintlayout.core.ArrayRow!);
+ method public void reset();
+ method public void setFinalValue(androidx.constraintlayout.core.LinearSystem!, float);
+ method public void setName(String!);
+ method public void setSynonym(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.SolverVariable!, float);
+ method public void setType(androidx.constraintlayout.core.SolverVariable.Type!, String!);
+ method public final void updateReferencesWithNewDefinition(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.ArrayRow!);
+ field public static final int STRENGTH_BARRIER = 6; // 0x6
+ field public static final int STRENGTH_CENTERING = 7; // 0x7
+ field public static final int STRENGTH_EQUALITY = 5; // 0x5
+ field public static final int STRENGTH_FIXED = 8; // 0x8
+ field public static final int STRENGTH_HIGH = 3; // 0x3
+ field public static final int STRENGTH_HIGHEST = 4; // 0x4
+ field public static final int STRENGTH_LOW = 1; // 0x1
+ field public static final int STRENGTH_MEDIUM = 2; // 0x2
+ field public static final int STRENGTH_NONE = 0; // 0x0
+ field public float computedValue;
+ field public int id;
+ field public boolean inGoal;
+ field public boolean isFinalValue;
+ field public int strength;
+ field public int usageInRowCount;
+ }
+
+ public enum SolverVariable.Type {
+ enum_constant public static final androidx.constraintlayout.core.SolverVariable.Type CONSTANT;
+ enum_constant public static final androidx.constraintlayout.core.SolverVariable.Type ERROR;
+ enum_constant public static final androidx.constraintlayout.core.SolverVariable.Type SLACK;
+ enum_constant public static final androidx.constraintlayout.core.SolverVariable.Type UNKNOWN;
+ enum_constant public static final androidx.constraintlayout.core.SolverVariable.Type UNRESTRICTED;
+ }
+
+ public class SolverVariableValues implements androidx.constraintlayout.core.ArrayRow.ArrayRowVariables {
+ method public void add(androidx.constraintlayout.core.SolverVariable!, float, boolean);
+ method public void clear();
+ method public boolean contains(androidx.constraintlayout.core.SolverVariable!);
+ method public void display();
+ method public void divideByAmount(float);
+ method public float get(androidx.constraintlayout.core.SolverVariable!);
+ method public int getCurrentSize();
+ method public androidx.constraintlayout.core.SolverVariable! getVariable(int);
+ method public float getVariableValue(int);
+ method public int indexOf(androidx.constraintlayout.core.SolverVariable!);
+ method public void invert();
+ method public void put(androidx.constraintlayout.core.SolverVariable!, float);
+ method public float remove(androidx.constraintlayout.core.SolverVariable!, boolean);
+ method public int sizeInBytes();
+ method public float use(androidx.constraintlayout.core.ArrayRow!, boolean);
+ field protected final androidx.constraintlayout.core.Cache! mCache;
+ }
+
+}
+
+package androidx.constraintlayout.core.dsl {
+
+ public class Barrier extends androidx.constraintlayout.core.dsl.Helper {
+ ctor public Barrier(String!);
+ ctor public Barrier(String!, String!);
+ method public androidx.constraintlayout.core.dsl.Barrier! addReference(androidx.constraintlayout.core.dsl.Ref!);
+ method public androidx.constraintlayout.core.dsl.Barrier! addReference(String!);
+ method public androidx.constraintlayout.core.dsl.Constraint.Side! getDirection();
+ method public int getMargin();
+ method public String! referencesToString();
+ method public void setDirection(androidx.constraintlayout.core.dsl.Constraint.Side!);
+ method public void setMargin(int);
+ }
+
+ public abstract class Chain extends androidx.constraintlayout.core.dsl.Helper {
+ ctor public Chain(String!);
+ method public androidx.constraintlayout.core.dsl.Chain! addReference(androidx.constraintlayout.core.dsl.Ref!);
+ method public androidx.constraintlayout.core.dsl.Chain! addReference(String!);
+ method public androidx.constraintlayout.core.dsl.Chain.Style! getStyle();
+ method public String! referencesToString();
+ method public void setStyle(androidx.constraintlayout.core.dsl.Chain.Style!);
+ field protected java.util.ArrayList<androidx.constraintlayout.core.dsl.Ref!>! references;
+ field protected static final java.util.Map<androidx.constraintlayout.core.dsl.Chain.Style!,java.lang.String!>! styleMap;
+ }
+
+ public class Chain.Anchor {
+ method public void build(StringBuilder!);
+ method public String! getId();
+ }
+
+ public enum Chain.Style {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Chain.Style PACKED;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Chain.Style SPREAD;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Chain.Style SPREAD_INSIDE;
+ }
+
+ public class Constraint {
+ ctor public Constraint(String!);
+ method protected void append(StringBuilder!, String!, float);
+ method public String! convertStringArrayToString(String![]!);
+ method public androidx.constraintlayout.core.dsl.Constraint.VAnchor! getBaseline();
+ method public androidx.constraintlayout.core.dsl.Constraint.VAnchor! getBottom();
+ method public float getCircleAngle();
+ method public String! getCircleConstraint();
+ method public int getCircleRadius();
+ method public String! getDimensionRatio();
+ method public int getEditorAbsoluteX();
+ method public int getEditorAbsoluteY();
+ method public androidx.constraintlayout.core.dsl.Constraint.HAnchor! getEnd();
+ method public int getHeight();
+ method public androidx.constraintlayout.core.dsl.Constraint.Behaviour! getHeightDefault();
+ method public int getHeightMax();
+ method public int getHeightMin();
+ method public float getHeightPercent();
+ method public float getHorizontalBias();
+ method public androidx.constraintlayout.core.dsl.Constraint.ChainMode! getHorizontalChainStyle();
+ method public float getHorizontalWeight();
+ method public androidx.constraintlayout.core.dsl.Constraint.HAnchor! getLeft();
+ method public String![]! getReferenceIds();
+ method public androidx.constraintlayout.core.dsl.Constraint.HAnchor! getRight();
+ method public androidx.constraintlayout.core.dsl.Constraint.HAnchor! getStart();
+ method public androidx.constraintlayout.core.dsl.Constraint.VAnchor! getTop();
+ method public float getVerticalBias();
+ method public androidx.constraintlayout.core.dsl.Constraint.ChainMode! getVerticalChainStyle();
+ method public float getVerticalWeight();
+ method public int getWidth();
+ method public androidx.constraintlayout.core.dsl.Constraint.Behaviour! getWidthDefault();
+ method public int getWidthMax();
+ method public int getWidthMin();
+ method public float getWidthPercent();
+ method public boolean isConstrainedHeight();
+ method public boolean isConstrainedWidth();
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ method public void setCircleAngle(float);
+ method public void setCircleConstraint(String!);
+ method public void setCircleRadius(int);
+ method public void setConstrainedHeight(boolean);
+ method public void setConstrainedWidth(boolean);
+ method public void setDimensionRatio(String!);
+ method public void setEditorAbsoluteX(int);
+ method public void setEditorAbsoluteY(int);
+ method public void setHeight(int);
+ method public void setHeightDefault(androidx.constraintlayout.core.dsl.Constraint.Behaviour!);
+ method public void setHeightMax(int);
+ method public void setHeightMin(int);
+ method public void setHeightPercent(float);
+ method public void setHorizontalBias(float);
+ method public void setHorizontalChainStyle(androidx.constraintlayout.core.dsl.Constraint.ChainMode!);
+ method public void setHorizontalWeight(float);
+ method public void setReferenceIds(String![]!);
+ method public void setVerticalBias(float);
+ method public void setVerticalChainStyle(androidx.constraintlayout.core.dsl.Constraint.ChainMode!);
+ method public void setVerticalWeight(float);
+ method public void setWidth(int);
+ method public void setWidthDefault(androidx.constraintlayout.core.dsl.Constraint.Behaviour!);
+ method public void setWidthMax(int);
+ method public void setWidthMin(int);
+ method public void setWidthPercent(float);
+ field public static final androidx.constraintlayout.core.dsl.Constraint! PARENT;
+ }
+
+ public class Constraint.Anchor {
+ method public void build(StringBuilder!);
+ method public String! getId();
+ }
+
+ public enum Constraint.Behaviour {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Behaviour PERCENT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Behaviour RATIO;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Behaviour RESOLVED;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Behaviour SPREAD;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Behaviour WRAP;
+ }
+
+ public enum Constraint.ChainMode {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.ChainMode PACKED;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.ChainMode SPREAD;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.ChainMode SPREAD_INSIDE;
+ }
+
+ public class Constraint.HAnchor extends androidx.constraintlayout.core.dsl.Constraint.Anchor {
+ }
+
+ public enum Constraint.HSide {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.HSide END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.HSide LEFT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.HSide RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.HSide START;
+ }
+
+ public enum Constraint.Side {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side LEFT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side START;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side TOP;
+ }
+
+ public class Constraint.VAnchor extends androidx.constraintlayout.core.dsl.Constraint.Anchor {
+ }
+
+ public enum Constraint.VSide {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.VSide BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.VSide BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.VSide TOP;
+ }
+
+ public class ConstraintSet {
+ ctor public ConstraintSet(String!);
+ method public void add(androidx.constraintlayout.core.dsl.Constraint!);
+ method public void add(androidx.constraintlayout.core.dsl.Helper!);
+ }
+
+ public abstract class Guideline extends androidx.constraintlayout.core.dsl.Helper {
+ method public int getEnd();
+ method public float getPercent();
+ method public int getStart();
+ method public void setEnd(int);
+ method public void setPercent(float);
+ method public void setStart(int);
+ }
+
+ public class HChain extends androidx.constraintlayout.core.dsl.Chain {
+ ctor public HChain(String!);
+ ctor public HChain(String!, String!);
+ method public androidx.constraintlayout.core.dsl.HChain.HAnchor! getEnd();
+ method public androidx.constraintlayout.core.dsl.HChain.HAnchor! getLeft();
+ method public androidx.constraintlayout.core.dsl.HChain.HAnchor! getRight();
+ method public androidx.constraintlayout.core.dsl.HChain.HAnchor! getStart();
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ }
+
+ public class HChain.HAnchor extends androidx.constraintlayout.core.dsl.Chain.Anchor {
+ }
+
+ public class Helper {
+ ctor public Helper(String!, androidx.constraintlayout.core.dsl.Helper.HelperType!);
+ ctor public Helper(String!, androidx.constraintlayout.core.dsl.Helper.HelperType!, String!);
+ method public void append(java.util.Map<java.lang.String!,java.lang.String!>!, StringBuilder!);
+ method public java.util.Map<java.lang.String!,java.lang.String!>! convertConfigToMap();
+ method public String! getConfig();
+ method public String! getId();
+ method public androidx.constraintlayout.core.dsl.Helper.HelperType! getType();
+ method public static void main(String![]!);
+ field protected String! config;
+ field protected java.util.Map<java.lang.String!,java.lang.String!>! configMap;
+ field protected final String! name;
+ field protected static final java.util.Map<androidx.constraintlayout.core.dsl.Constraint.Side!,java.lang.String!>! sideMap;
+ field protected androidx.constraintlayout.core.dsl.Helper.HelperType! type;
+ field protected static final java.util.Map<androidx.constraintlayout.core.dsl.Helper.Type!,java.lang.String!>! typeMap;
+ }
+
+ public static final class Helper.HelperType {
+ ctor public Helper.HelperType(String!);
+ }
+
+ public enum Helper.Type {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Helper.Type BARRIER;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Helper.Type HORIZONTAL_CHAIN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Helper.Type HORIZONTAL_GUIDELINE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Helper.Type VERTICAL_CHAIN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Helper.Type VERTICAL_GUIDELINE;
+ }
+
+ public class KeyAttribute extends androidx.constraintlayout.core.dsl.Keys {
+ ctor public KeyAttribute(int, String!);
+ method protected void attributesToString(StringBuilder!);
+ method public float getAlpha();
+ method public androidx.constraintlayout.core.dsl.KeyAttribute.Fit! getCurveFit();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public String! getTarget();
+ method public String! getTransitionEasing();
+ method public float getTransitionPathRotate();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public androidx.constraintlayout.core.dsl.KeyAttribute.Visibility! getVisibility();
+ method public void setAlpha(float);
+ method public void setCurveFit(androidx.constraintlayout.core.dsl.KeyAttribute.Fit!);
+ method public void setPivotX(float);
+ method public void setPivotY(float);
+ method public void setRotation(float);
+ method public void setRotationX(float);
+ method public void setRotationY(float);
+ method public void setScaleX(float);
+ method public void setScaleY(float);
+ method public void setTarget(String!);
+ method public void setTransitionEasing(String!);
+ method public void setTransitionPathRotate(float);
+ method public void setTranslationX(float);
+ method public void setTranslationY(float);
+ method public void setTranslationZ(float);
+ method public void setVisibility(androidx.constraintlayout.core.dsl.KeyAttribute.Visibility!);
+ field protected String! TYPE;
+ }
+
+ public enum KeyAttribute.Fit {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttribute.Fit LINEAR;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttribute.Fit SPLINE;
+ }
+
+ public enum KeyAttribute.Visibility {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttribute.Visibility GONE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttribute.Visibility INVISIBLE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttribute.Visibility VISIBLE;
+ }
+
+ public class KeyAttributes extends androidx.constraintlayout.core.dsl.Keys {
+ method protected void attributesToString(StringBuilder!);
+ method public float[]! getAlpha();
+ method public androidx.constraintlayout.core.dsl.KeyAttributes.Fit! getCurveFit();
+ method public float[]! getPivotX();
+ method public float[]! getPivotY();
+ method public float[]! getRotation();
+ method public float[]! getRotationX();
+ method public float[]! getRotationY();
+ method public float[]! getScaleX();
+ method public float[]! getScaleY();
+ method public String![]! getTarget();
+ method public String! getTransitionEasing();
+ method public float[]! getTransitionPathRotate();
+ method public float[]! getTranslationX();
+ method public float[]! getTranslationY();
+ method public float[]! getTranslationZ();
+ method public androidx.constraintlayout.core.dsl.KeyAttributes.Visibility![]! getVisibility();
+ method public void setAlpha(float...!);
+ method public void setCurveFit(androidx.constraintlayout.core.dsl.KeyAttributes.Fit!);
+ method public void setPivotX(float...!);
+ method public void setPivotY(float...!);
+ method public void setRotation(float...!);
+ method public void setRotationX(float...!);
+ method public void setRotationY(float...!);
+ method public void setScaleX(float[]!);
+ method public void setScaleY(float[]!);
+ method public void setTarget(String![]!);
+ method public void setTransitionEasing(String!);
+ method public void setTransitionPathRotate(float...!);
+ method public void setTranslationX(float[]!);
+ method public void setTranslationY(float[]!);
+ method public void setTranslationZ(float[]!);
+ method public void setVisibility(androidx.constraintlayout.core.dsl.KeyAttributes.Visibility!...!);
+ field protected String! TYPE;
+ }
+
+ public enum KeyAttributes.Fit {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttributes.Fit LINEAR;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttributes.Fit SPLINE;
+ }
+
+ public enum KeyAttributes.Visibility {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttributes.Visibility GONE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttributes.Visibility INVISIBLE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttributes.Visibility VISIBLE;
+ }
+
+ public class KeyCycle extends androidx.constraintlayout.core.dsl.KeyAttribute {
+ method public float getOffset();
+ method public float getPeriod();
+ method public float getPhase();
+ method public androidx.constraintlayout.core.dsl.KeyCycle.Wave! getShape();
+ method public void setOffset(float);
+ method public void setPeriod(float);
+ method public void setPhase(float);
+ method public void setShape(androidx.constraintlayout.core.dsl.KeyCycle.Wave!);
+ }
+
+ public enum KeyCycle.Wave {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave COS;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave REVERSE_SAW;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave SAW;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave SIN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave SQUARE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave TRIANGLE;
+ }
+
+ public class KeyCycles extends androidx.constraintlayout.core.dsl.KeyAttributes {
+ method public float[]! getWaveOffset();
+ method public float[]! getWavePeriod();
+ method public float[]! getWavePhase();
+ method public androidx.constraintlayout.core.dsl.KeyCycles.Wave! getWaveShape();
+ method public void setWaveOffset(float...!);
+ method public void setWavePeriod(float...!);
+ method public void setWavePhase(float...!);
+ method public void setWaveShape(androidx.constraintlayout.core.dsl.KeyCycles.Wave!);
+ }
+
+ public enum KeyCycles.Wave {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave COS;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave REVERSE_SAW;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave SAW;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave SIN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave SQUARE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave TRIANGLE;
+ }
+
+ public class KeyFrames {
+ ctor public KeyFrames();
+ method public void add(androidx.constraintlayout.core.dsl.Keys!);
+ }
+
+ public class KeyPosition extends androidx.constraintlayout.core.dsl.Keys {
+ ctor public KeyPosition(String!, int);
+ method public int getFrames();
+ method public float getPercentHeight();
+ method public float getPercentWidth();
+ method public float getPercentX();
+ method public float getPercentY();
+ method public androidx.constraintlayout.core.dsl.KeyPosition.Type! getPositionType();
+ method public String! getTarget();
+ method public String! getTransitionEasing();
+ method public void setFrames(int);
+ method public void setPercentHeight(float);
+ method public void setPercentWidth(float);
+ method public void setPercentX(float);
+ method public void setPercentY(float);
+ method public void setPositionType(androidx.constraintlayout.core.dsl.KeyPosition.Type!);
+ method public void setTarget(String!);
+ method public void setTransitionEasing(String!);
+ }
+
+ public enum KeyPosition.Type {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPosition.Type CARTESIAN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPosition.Type PATH;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPosition.Type SCREEN;
+ }
+
+ public class KeyPositions extends androidx.constraintlayout.core.dsl.Keys {
+ ctor public KeyPositions(int, java.lang.String!...!);
+ method public int[]! getFrames();
+ method public float[]! getPercentHeight();
+ method public float[]! getPercentWidth();
+ method public float[]! getPercentX();
+ method public float[]! getPercentY();
+ method public androidx.constraintlayout.core.dsl.KeyPositions.Type! getPositionType();
+ method public String![]! getTarget();
+ method public String! getTransitionEasing();
+ method public void setFrames(int...!);
+ method public void setPercentHeight(float...!);
+ method public void setPercentWidth(float...!);
+ method public void setPercentX(float...!);
+ method public void setPercentY(float...!);
+ method public void setPositionType(androidx.constraintlayout.core.dsl.KeyPositions.Type!);
+ method public void setTransitionEasing(String!);
+ }
+
+ public enum KeyPositions.Type {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPositions.Type CARTESIAN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPositions.Type PATH;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPositions.Type SCREEN;
+ }
+
+ public class Keys {
+ ctor public Keys();
+ method protected void append(StringBuilder!, String!, float);
+ method protected void append(StringBuilder!, String!, float[]!);
+ method protected void append(StringBuilder!, String!, int);
+ method protected void append(StringBuilder!, String!, String!);
+ method protected void append(StringBuilder!, String!, String![]!);
+ method protected String! unpack(String![]!);
+ }
+
+ public class MotionScene {
+ ctor public MotionScene();
+ method public void addConstraintSet(androidx.constraintlayout.core.dsl.ConstraintSet!);
+ method public void addTransition(androidx.constraintlayout.core.dsl.Transition!);
+ }
+
+ public class OnSwipe {
+ ctor public OnSwipe();
+ ctor public OnSwipe(String!, androidx.constraintlayout.core.dsl.OnSwipe.Side!, androidx.constraintlayout.core.dsl.OnSwipe.Drag!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe.Mode! getAutoCompleteMode();
+ method public androidx.constraintlayout.core.dsl.OnSwipe.Drag! getDragDirection();
+ method public float getDragScale();
+ method public float getDragThreshold();
+ method public String! getLimitBoundsTo();
+ method public float getMaxAcceleration();
+ method public float getMaxVelocity();
+ method public androidx.constraintlayout.core.dsl.OnSwipe.TouchUp! getOnTouchUp();
+ method public String! getRotationCenterId();
+ method public androidx.constraintlayout.core.dsl.OnSwipe.Boundary! getSpringBoundary();
+ method public float getSpringDamping();
+ method public float getSpringMass();
+ method public float getSpringStiffness();
+ method public float getSpringStopThreshold();
+ method public String! getTouchAnchorId();
+ method public androidx.constraintlayout.core.dsl.OnSwipe.Side! getTouchAnchorSide();
+ method public void setAutoCompleteMode(androidx.constraintlayout.core.dsl.OnSwipe.Mode!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setDragDirection(androidx.constraintlayout.core.dsl.OnSwipe.Drag!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setDragScale(int);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setDragThreshold(int);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setLimitBoundsTo(String!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setMaxAcceleration(int);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setMaxVelocity(int);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setOnTouchUp(androidx.constraintlayout.core.dsl.OnSwipe.TouchUp!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setRotateCenter(String!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setSpringBoundary(androidx.constraintlayout.core.dsl.OnSwipe.Boundary!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setSpringDamping(float);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setSpringMass(float);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setSpringStiffness(float);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setSpringStopThreshold(float);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setTouchAnchorId(String!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setTouchAnchorSide(androidx.constraintlayout.core.dsl.OnSwipe.Side!);
+ field public static final int FLAG_DISABLE_POST_SCROLL = 1; // 0x1
+ field public static final int FLAG_DISABLE_SCROLL = 2; // 0x2
+ }
+
+ public enum OnSwipe.Boundary {
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Boundary BOUNCE_BOTH;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Boundary BOUNCE_END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Boundary BOUNCE_START;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Boundary OVERSHOOT;
+ }
+
+ public enum OnSwipe.Drag {
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag ANTICLOCKWISE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag CLOCKWISE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag DOWN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag LEFT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag START;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag UP;
+ }
+
+ public enum OnSwipe.Mode {
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Mode SPRING;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Mode VELOCITY;
+ }
+
+ public enum OnSwipe.Side {
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side LEFT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side MIDDLE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side START;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side TOP;
+ }
+
+ public enum OnSwipe.TouchUp {
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp AUTOCOMPLETE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp DECELERATE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp DECELERATE_COMPLETE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp NEVER_COMPLETE_END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp NEVER_COMPLETE_START;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp STOP;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp TO_END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp TO_START;
+ }
+
+ public class Ref {
+ method public static void addStringToReferences(String!, java.util.ArrayList<androidx.constraintlayout.core.dsl.Ref!>!);
+ method public String! getId();
+ method public float getPostMargin();
+ method public float getPreMargin();
+ method public float getWeight();
+ method public static float parseFloat(Object!);
+ method public static androidx.constraintlayout.core.dsl.Ref! parseStringToRef(String!);
+ method public void setId(String!);
+ method public void setPostMargin(float);
+ method public void setPreMargin(float);
+ method public void setWeight(float);
+ }
+
+ public class Transition {
+ ctor public Transition(String!, String!);
+ ctor public Transition(String!, String!, String!);
+ method public String! getId();
+ method public void setDuration(int);
+ method public void setFrom(String!);
+ method public void setId(String!);
+ method public void setKeyFrames(androidx.constraintlayout.core.dsl.Keys!);
+ method public void setOnSwipe(androidx.constraintlayout.core.dsl.OnSwipe!);
+ method public void setStagger(float);
+ method public void setTo(String!);
+ }
+
+ public class VChain extends androidx.constraintlayout.core.dsl.Chain {
+ ctor public VChain(String!);
+ ctor public VChain(String!, String!);
+ method public androidx.constraintlayout.core.dsl.VChain.VAnchor! getBaseline();
+ method public androidx.constraintlayout.core.dsl.VChain.VAnchor! getBottom();
+ method public androidx.constraintlayout.core.dsl.VChain.VAnchor! getTop();
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ }
+
+ public class VChain.VAnchor extends androidx.constraintlayout.core.dsl.Chain.Anchor {
+ }
+
+ public class VGuideline extends androidx.constraintlayout.core.dsl.Guideline {
+ ctor public VGuideline(String!);
+ ctor public VGuideline(String!, String!);
+ }
+
+}
+
+package androidx.constraintlayout.core.motion {
+
+ public class CustomAttribute {
+ ctor public CustomAttribute(androidx.constraintlayout.core.motion.CustomAttribute!, Object!);
+ ctor public CustomAttribute(String!, androidx.constraintlayout.core.motion.CustomAttribute.AttributeType!);
+ ctor public CustomAttribute(String!, androidx.constraintlayout.core.motion.CustomAttribute.AttributeType!, Object!, boolean);
+ method public boolean diff(androidx.constraintlayout.core.motion.CustomAttribute!);
+ method public androidx.constraintlayout.core.motion.CustomAttribute.AttributeType! getType();
+ method public float getValueToInterpolate();
+ method public void getValuesToInterpolate(float[]!);
+ method public static int hsvToRgb(float, float, float);
+ method public boolean isContinuous();
+ method public int numberOfInterpolatedValues();
+ method public void setColorValue(int);
+ method public void setFloatValue(float);
+ method public void setIntValue(int);
+ method public void setStringValue(String!);
+ method public void setValue(float[]!);
+ method public void setValue(Object!);
+ }
+
+ public enum CustomAttribute.AttributeType {
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType BOOLEAN_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType COLOR_DRAWABLE_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType COLOR_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType DIMENSION_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType FLOAT_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType INT_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType REFERENCE_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType STRING_TYPE;
+ }
+
+ public class CustomVariable {
+ ctor public CustomVariable(androidx.constraintlayout.core.motion.CustomVariable!);
+ ctor public CustomVariable(androidx.constraintlayout.core.motion.CustomVariable!, Object!);
+ ctor public CustomVariable(String!, int);
+ ctor public CustomVariable(String!, int, boolean);
+ ctor public CustomVariable(String!, int, float);
+ ctor public CustomVariable(String!, int, int);
+ ctor public CustomVariable(String!, int, Object!);
+ ctor public CustomVariable(String!, int, String!);
+ method public void applyToWidget(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public static String! colorString(int);
+ method public androidx.constraintlayout.core.motion.CustomVariable! copy();
+ method public boolean diff(androidx.constraintlayout.core.motion.CustomVariable!);
+ method public boolean getBooleanValue();
+ method public int getColorValue();
+ method public float getFloatValue();
+ method public int getIntegerValue();
+ method public int getInterpolatedColor(float[]!);
+ method public String! getName();
+ method public String! getStringValue();
+ method public int getType();
+ method public float getValueToInterpolate();
+ method public void getValuesToInterpolate(float[]!);
+ method public static int hsvToRgb(float, float, float);
+ method public boolean isContinuous();
+ method public int numberOfInterpolatedValues();
+ method public static int rgbaTocColor(float, float, float, float);
+ method public void setBooleanValue(boolean);
+ method public void setFloatValue(float);
+ method public void setIntValue(int);
+ method public void setInterpolatedValue(androidx.constraintlayout.core.motion.MotionWidget!, float[]!);
+ method public void setStringValue(String!);
+ method public void setValue(float[]!);
+ method public void setValue(Object!);
+ }
+
+ public class Motion implements androidx.constraintlayout.core.motion.utils.TypedValues {
+ ctor public Motion(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public void addKey(androidx.constraintlayout.core.motion.key.MotionKey!);
+ method public int buildKeyFrames(float[]!, int[]!, int[]!);
+ method public void buildPath(float[]!, int);
+ method public void buildRect(float, float[]!, int);
+ method public String! getAnimateRelativeTo();
+ method public void getCenter(double, float[]!, float[]!);
+ method public float getCenterX();
+ method public float getCenterY();
+ method public void getDpDt(float, float, float, float[]!);
+ method public int getDrawPath();
+ method public float getFinalHeight();
+ method public float getFinalWidth();
+ method public float getFinalX();
+ method public float getFinalY();
+ method public int getId(String!);
+ method public androidx.constraintlayout.core.motion.MotionPaths! getKeyFrame(int);
+ method public int getKeyFrameInfo(int, int[]!);
+ method public int getKeyFramePositions(int[]!, float[]!);
+ method public float getMotionStagger();
+ method public float getStartHeight();
+ method public float getStartWidth();
+ method public float getStartX();
+ method public float getStartY();
+ method public int getTransformPivotTarget();
+ method public androidx.constraintlayout.core.motion.MotionWidget! getView();
+ method public boolean interpolate(androidx.constraintlayout.core.motion.MotionWidget!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ method public void setDrawPath(int);
+ method public void setEnd(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public void setIdString(String!);
+ method public void setPathMotionArc(int);
+ method public void setStaggerOffset(float);
+ method public void setStaggerScale(float);
+ method public void setStart(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public void setStartState(androidx.constraintlayout.core.motion.utils.ViewState!, androidx.constraintlayout.core.motion.MotionWidget!, int, int, int);
+ method public void setTransformPivotTarget(int);
+ method public boolean setValue(int, boolean);
+ method public boolean setValue(int, float);
+ method public boolean setValue(int, int);
+ method public boolean setValue(int, String!);
+ method public void setView(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public void setup(int, int, float, long);
+ method public void setupRelative(androidx.constraintlayout.core.motion.Motion!);
+ field public static final int DRAW_PATH_AS_CONFIGURED = 4; // 0x4
+ field public static final int DRAW_PATH_BASIC = 1; // 0x1
+ field public static final int DRAW_PATH_CARTESIAN = 3; // 0x3
+ field public static final int DRAW_PATH_NONE = 0; // 0x0
+ field public static final int DRAW_PATH_RECTANGLE = 5; // 0x5
+ field public static final int DRAW_PATH_RELATIVE = 2; // 0x2
+ field public static final int DRAW_PATH_SCREEN = 6; // 0x6
+ field public static final int HORIZONTAL_PATH_X = 2; // 0x2
+ field public static final int HORIZONTAL_PATH_Y = 3; // 0x3
+ field public static final int PATH_PERCENT = 0; // 0x0
+ field public static final int PATH_PERPENDICULAR = 1; // 0x1
+ field public static final int ROTATION_LEFT = 2; // 0x2
+ field public static final int ROTATION_RIGHT = 1; // 0x1
+ field public static final int VERTICAL_PATH_X = 4; // 0x4
+ field public static final int VERTICAL_PATH_Y = 5; // 0x5
+ field public String! mId;
+ }
+
+ public class MotionPaths implements java.lang.Comparable<androidx.constraintlayout.core.motion.MotionPaths!> {
+ ctor public MotionPaths();
+ ctor public MotionPaths(int, int, androidx.constraintlayout.core.motion.key.MotionKeyPosition!, androidx.constraintlayout.core.motion.MotionPaths!, androidx.constraintlayout.core.motion.MotionPaths!);
+ method public void applyParameters(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public int compareTo(androidx.constraintlayout.core.motion.MotionPaths!);
+ method public void configureRelativeTo(androidx.constraintlayout.core.motion.Motion!);
+ method public void setupRelative(androidx.constraintlayout.core.motion.Motion!, androidx.constraintlayout.core.motion.MotionPaths!);
+ field public static final int CARTESIAN = 0; // 0x0
+ field public static final boolean DEBUG = false;
+ field public static final boolean OLD_WAY = false;
+ field public static final int PERPENDICULAR = 1; // 0x1
+ field public static final int SCREEN = 2; // 0x2
+ field public static final String TAG = "MotionPaths";
+ field public String! mId;
+ }
+
+ public class MotionWidget implements androidx.constraintlayout.core.motion.utils.TypedValues {
+ ctor public MotionWidget();
+ ctor public MotionWidget(androidx.constraintlayout.core.state.WidgetFrame!);
+ method public androidx.constraintlayout.core.motion.MotionWidget! findViewById(int);
+ method public float getAlpha();
+ method public int getBottom();
+ method public androidx.constraintlayout.core.motion.CustomVariable! getCustomAttribute(String!);
+ method public java.util.Set<java.lang.String!>! getCustomAttributeNames();
+ method public int getHeight();
+ method public int getId(String!);
+ method public int getLeft();
+ method public String! getName();
+ method public androidx.constraintlayout.core.motion.MotionWidget! getParent();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public int getRight();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getRotationZ();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getTop();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public float getValueAttributes(int);
+ method public int getVisibility();
+ method public androidx.constraintlayout.core.state.WidgetFrame! getWidgetFrame();
+ method public int getWidth();
+ method public int getX();
+ method public int getY();
+ method public void layout(int, int, int, int);
+ method public void setBounds(int, int, int, int);
+ method public void setCustomAttribute(String!, int, boolean);
+ method public void setCustomAttribute(String!, int, float);
+ method public void setCustomAttribute(String!, int, int);
+ method public void setCustomAttribute(String!, int, String!);
+ method public void setInterpolatedValue(androidx.constraintlayout.core.motion.CustomAttribute!, float[]!);
+ method public void setPivotX(float);
+ method public void setPivotY(float);
+ method public void setRotationX(float);
+ method public void setRotationY(float);
+ method public void setRotationZ(float);
+ method public void setScaleX(float);
+ method public void setScaleY(float);
+ method public void setTranslationX(float);
+ method public void setTranslationY(float);
+ method public void setTranslationZ(float);
+ method public boolean setValue(int, boolean);
+ method public boolean setValue(int, float);
+ method public boolean setValue(int, int);
+ method public boolean setValue(int, String!);
+ method public boolean setValueAttributes(int, float);
+ method public boolean setValueMotion(int, float);
+ method public boolean setValueMotion(int, int);
+ method public boolean setValueMotion(int, String!);
+ method public void setVisibility(int);
+ method public void updateMotion(androidx.constraintlayout.core.motion.utils.TypedValues!);
+ field public static final int FILL_PARENT = -1; // 0xffffffff
+ field public static final int GONE_UNSET = -2147483648; // 0x80000000
+ field public static final int INVISIBLE = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_WRAP = 1; // 0x1
+ field public static final int MATCH_PARENT = -1; // 0xffffffff
+ field public static final int PARENT_ID = 0; // 0x0
+ field public static final int ROTATE_LEFT_OF_PORTRATE = 4; // 0x4
+ field public static final int ROTATE_NONE = 0; // 0x0
+ field public static final int ROTATE_PORTRATE_OF_LEFT = 2; // 0x2
+ field public static final int ROTATE_PORTRATE_OF_RIGHT = 1; // 0x1
+ field public static final int ROTATE_RIGHT_OF_PORTRATE = 3; // 0x3
+ field public static final int UNSET = -1; // 0xffffffff
+ field public static final int VISIBILITY_MODE_IGNORE = 1; // 0x1
+ field public static final int VISIBILITY_MODE_NORMAL = 0; // 0x0
+ field public static final int VISIBLE = 4; // 0x4
+ field public static final int WRAP_CONTENT = -2; // 0xfffffffe
+ }
+
+ public static class MotionWidget.Motion {
+ ctor public MotionWidget.Motion();
+ field public int mAnimateCircleAngleTo;
+ field public String! mAnimateRelativeTo;
+ field public int mDrawPath;
+ field public float mMotionStagger;
+ field public int mPathMotionArc;
+ field public float mPathRotate;
+ field public int mPolarRelativeTo;
+ field public int mQuantizeInterpolatorID;
+ field public String! mQuantizeInterpolatorString;
+ field public int mQuantizeInterpolatorType;
+ field public float mQuantizeMotionPhase;
+ field public int mQuantizeMotionSteps;
+ field public String! mTransitionEasing;
+ }
+
+ public static class MotionWidget.PropertySet {
+ ctor public MotionWidget.PropertySet();
+ field public float alpha;
+ field public float mProgress;
+ field public int mVisibilityMode;
+ field public int visibility;
+ }
+
+}
+
+package androidx.constraintlayout.core.motion.key {
+
+ public class MotionConstraintSet {
+ ctor public MotionConstraintSet();
+ field public static final int ROTATE_LEFT_OF_PORTRATE = 4; // 0x4
+ field public static final int ROTATE_NONE = 0; // 0x0
+ field public static final int ROTATE_PORTRATE_OF_LEFT = 2; // 0x2
+ field public static final int ROTATE_PORTRATE_OF_RIGHT = 1; // 0x1
+ field public static final int ROTATE_RIGHT_OF_PORTRATE = 3; // 0x3
+ field public String! mIdString;
+ field public int mRotate;
+ }
+
+ public abstract class MotionKey implements androidx.constraintlayout.core.motion.utils.TypedValues {
+ ctor public MotionKey();
+ method public abstract void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public abstract androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public androidx.constraintlayout.core.motion.key.MotionKey! copy(androidx.constraintlayout.core.motion.key.MotionKey!);
+ method public abstract void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getFramePosition();
+ method public void setCustomAttribute(String!, int, boolean);
+ method public void setCustomAttribute(String!, int, float);
+ method public void setCustomAttribute(String!, int, int);
+ method public void setCustomAttribute(String!, int, String!);
+ method public void setFramePosition(int);
+ method public void setInterpolation(java.util.HashMap<java.lang.String!,java.lang.Integer!>!);
+ method public boolean setValue(int, boolean);
+ method public boolean setValue(int, float);
+ method public boolean setValue(int, int);
+ method public boolean setValue(int, String!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! setViewId(int);
+ field public static final String ALPHA = "alpha";
+ field public static final String CUSTOM = "CUSTOM";
+ field public static final String ELEVATION = "elevation";
+ field public static final String ROTATION = "rotationZ";
+ field public static final String ROTATION_X = "rotationX";
+ field public static final String SCALE_X = "scaleX";
+ field public static final String SCALE_Y = "scaleY";
+ field public static final String TRANSITION_PATH_ROTATE = "transitionPathRotate";
+ field public static final String TRANSLATION_X = "translationX";
+ field public static final String TRANSLATION_Y = "translationY";
+ field public static int UNSET;
+ field public static final String VISIBILITY = "visibility";
+ field public java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.CustomVariable!>! mCustom;
+ field public int mFramePosition;
+ field public int mType;
+ }
+
+ public class MotionKeyAttributes extends androidx.constraintlayout.core.motion.key.MotionKey {
+ ctor public MotionKeyAttributes();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getCurveFit();
+ method public int getId(String!);
+ method public void printAttributes();
+ field public static final int KEY_TYPE = 1; // 0x1
+ }
+
+ public class MotionKeyCycle extends androidx.constraintlayout.core.motion.key.MotionKey {
+ ctor public MotionKeyCycle();
+ method public void addCycleValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!>!);
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public void dump();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getId(String!);
+ method public float getValue(String!);
+ method public void printAttributes();
+ field public static final int KEY_TYPE = 4; // 0x4
+ field public static final int SHAPE_BOUNCE = 6; // 0x6
+ field public static final int SHAPE_COS_WAVE = 5; // 0x5
+ field public static final int SHAPE_REVERSE_SAW_WAVE = 4; // 0x4
+ field public static final int SHAPE_SAW_WAVE = 3; // 0x3
+ field public static final int SHAPE_SIN_WAVE = 0; // 0x0
+ field public static final int SHAPE_SQUARE_WAVE = 1; // 0x1
+ field public static final int SHAPE_TRIANGLE_WAVE = 2; // 0x2
+ field public static final String WAVE_OFFSET = "waveOffset";
+ field public static final String WAVE_PERIOD = "wavePeriod";
+ field public static final String WAVE_PHASE = "wavePhase";
+ field public static final String WAVE_SHAPE = "waveShape";
+ }
+
+ public class MotionKeyPosition extends androidx.constraintlayout.core.motion.key.MotionKey {
+ ctor public MotionKeyPosition();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getId(String!);
+ method public boolean intersects(int, int, androidx.constraintlayout.core.motion.utils.FloatRect!, androidx.constraintlayout.core.motion.utils.FloatRect!, float, float);
+ method public void positionAttributes(androidx.constraintlayout.core.motion.MotionWidget!, androidx.constraintlayout.core.motion.utils.FloatRect!, androidx.constraintlayout.core.motion.utils.FloatRect!, float, float, String![]!, float[]!);
+ field protected static final float SELECTION_SLOPE = 20.0f;
+ field public static final int TYPE_CARTESIAN = 0; // 0x0
+ field public static final int TYPE_PATH = 1; // 0x1
+ field public static final int TYPE_SCREEN = 2; // 0x2
+ field public float mAltPercentX;
+ field public float mAltPercentY;
+ field public int mCurveFit;
+ field public int mDrawPath;
+ field public int mPathMotionArc;
+ field public float mPercentHeight;
+ field public float mPercentWidth;
+ field public float mPercentX;
+ field public float mPercentY;
+ field public int mPositionType;
+ field public String! mTransitionEasing;
+ }
+
+ public class MotionKeyTimeCycle extends androidx.constraintlayout.core.motion.key.MotionKey {
+ ctor public MotionKeyTimeCycle();
+ method public void addTimeValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet!>!);
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public androidx.constraintlayout.core.motion.key.MotionKeyTimeCycle! copy(androidx.constraintlayout.core.motion.key.MotionKey!);
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getId(String!);
+ field public static final int KEY_TYPE = 3; // 0x3
+ }
+
+ public class MotionKeyTrigger extends androidx.constraintlayout.core.motion.key.MotionKey {
+ ctor public MotionKeyTrigger();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public void conditionallyFire(float, androidx.constraintlayout.core.motion.MotionWidget!);
+ method public androidx.constraintlayout.core.motion.key.MotionKeyTrigger! copy(androidx.constraintlayout.core.motion.key.MotionKey!);
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getId(String!);
+ field public static final String CROSS = "CROSS";
+ field public static final int KEY_TYPE = 5; // 0x5
+ field public static final String NEGATIVE_CROSS = "negativeCross";
+ field public static final String POSITIVE_CROSS = "positiveCross";
+ field public static final String POST_LAYOUT = "postLayout";
+ field public static final String TRIGGER_COLLISION_ID = "triggerCollisionId";
+ field public static final String TRIGGER_COLLISION_VIEW = "triggerCollisionView";
+ field public static final String TRIGGER_ID = "triggerID";
+ field public static final String TRIGGER_RECEIVER = "triggerReceiver";
+ field public static final String TRIGGER_SLACK = "triggerSlack";
+ field public static final int TYPE_CROSS = 312; // 0x138
+ field public static final int TYPE_NEGATIVE_CROSS = 310; // 0x136
+ field public static final int TYPE_POSITIVE_CROSS = 309; // 0x135
+ field public static final int TYPE_POST_LAYOUT = 304; // 0x130
+ field public static final int TYPE_TRIGGER_COLLISION_ID = 307; // 0x133
+ field public static final int TYPE_TRIGGER_COLLISION_VIEW = 306; // 0x132
+ field public static final int TYPE_TRIGGER_ID = 308; // 0x134
+ field public static final int TYPE_TRIGGER_RECEIVER = 311; // 0x137
+ field public static final int TYPE_TRIGGER_SLACK = 305; // 0x131
+ field public static final int TYPE_VIEW_TRANSITION_ON_CROSS = 301; // 0x12d
+ field public static final int TYPE_VIEW_TRANSITION_ON_NEGATIVE_CROSS = 303; // 0x12f
+ field public static final int TYPE_VIEW_TRANSITION_ON_POSITIVE_CROSS = 302; // 0x12e
+ field public static final String VIEW_TRANSITION_ON_CROSS = "viewTransitionOnCross";
+ field public static final String VIEW_TRANSITION_ON_NEGATIVE_CROSS = "viewTransitionOnNegativeCross";
+ field public static final String VIEW_TRANSITION_ON_POSITIVE_CROSS = "viewTransitionOnPositiveCross";
+ }
+
+}
+
+package androidx.constraintlayout.core.motion.parse {
+
+ public class KeyParser {
+ ctor public KeyParser();
+ method public static void main(String![]!);
+ method public static androidx.constraintlayout.core.motion.utils.TypedBundle! parseAttributes(String!);
+ }
+
+}
+
+package androidx.constraintlayout.core.motion.utils {
+
+ public class ArcCurveFit extends androidx.constraintlayout.core.motion.utils.CurveFit {
+ ctor public ArcCurveFit(int[]!, double[]!, double[]![]!);
+ method public void getPos(double, double[]!);
+ method public void getPos(double, float[]!);
+ method public double getPos(double, int);
+ method public void getSlope(double, double[]!);
+ method public double getSlope(double, int);
+ method public double[]! getTimePoints();
+ field public static final int ARC_ABOVE = 5; // 0x5
+ field public static final int ARC_BELOW = 4; // 0x4
+ field public static final int ARC_START_FLIP = 3; // 0x3
+ field public static final int ARC_START_HORIZONTAL = 2; // 0x2
+ field public static final int ARC_START_LINEAR = 0; // 0x0
+ field public static final int ARC_START_VERTICAL = 1; // 0x1
+ }
+
+ public abstract class CurveFit {
+ ctor public CurveFit();
+ method public static androidx.constraintlayout.core.motion.utils.CurveFit! get(int, double[]!, double[]![]!);
+ method public static androidx.constraintlayout.core.motion.utils.CurveFit! getArc(int[]!, double[]!, double[]![]!);
+ method public abstract void getPos(double, double[]!);
+ method public abstract void getPos(double, float[]!);
+ method public abstract double getPos(double, int);
+ method public abstract void getSlope(double, double[]!);
+ method public abstract double getSlope(double, int);
+ method public abstract double[]! getTimePoints();
+ field public static final int CONSTANT = 2; // 0x2
+ field public static final int LINEAR = 1; // 0x1
+ field public static final int SPLINE = 0; // 0x0
+ }
+
+ public interface DifferentialInterpolator {
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ }
+
+ public class Easing {
+ ctor public Easing();
+ method public double get(double);
+ method public double getDiff(double);
+ method public static androidx.constraintlayout.core.motion.utils.Easing! getInterpolator(String!);
+ field public static String![]! NAMED_EASING;
+ }
+
+ public class FloatRect {
+ ctor public FloatRect();
+ method public final float centerX();
+ method public final float centerY();
+ field public float bottom;
+ field public float left;
+ field public float right;
+ field public float top;
+ }
+
+ public class HyperSpline {
+ ctor public HyperSpline();
+ ctor public HyperSpline(double[]![]!);
+ method public double approxLength(androidx.constraintlayout.core.motion.utils.HyperSpline.Cubic![]!);
+ method public void getPos(double, double[]!);
+ method public void getPos(double, float[]!);
+ method public double getPos(double, int);
+ method public void getVelocity(double, double[]!);
+ method public void setup(double[]![]!);
+ }
+
+ public static class HyperSpline.Cubic {
+ ctor public HyperSpline.Cubic(double, double, double, double);
+ method public double eval(double);
+ method public double vel(double);
+ }
+
+ public class KeyCache {
+ ctor public KeyCache();
+ method public float getFloatValue(Object!, String!, int);
+ method public void setFloatValue(Object!, String!, int, float);
+ }
+
+ public abstract class KeyCycleOscillator {
+ ctor public KeyCycleOscillator();
+ method public float get(float);
+ method public androidx.constraintlayout.core.motion.utils.CurveFit! getCurveFit();
+ method public float getSlope(float);
+ method public static androidx.constraintlayout.core.motion.utils.KeyCycleOscillator! makeWidgetCycle(String!);
+ method protected void setCustom(Object!);
+ method public void setPoint(int, int, String!, int, float, float, float, float);
+ method public void setPoint(int, int, String!, int, float, float, float, float, Object!);
+ method public void setProperty(androidx.constraintlayout.core.motion.MotionWidget!, float);
+ method public void setType(String!);
+ method public void setup(float);
+ method public boolean variesByPath();
+ field public int mVariesBy;
+ }
+
+ public static class KeyCycleOscillator.PathRotateSet extends androidx.constraintlayout.core.motion.utils.KeyCycleOscillator {
+ ctor public KeyCycleOscillator.PathRotateSet(String!);
+ method public void setPathRotate(androidx.constraintlayout.core.motion.MotionWidget!, float, double, double);
+ }
+
+ public class KeyFrameArray {
+ ctor public KeyFrameArray();
+ }
+
+ public static class KeyFrameArray.CustomArray {
+ ctor public KeyFrameArray.CustomArray();
+ method public void append(int, androidx.constraintlayout.core.motion.CustomAttribute!);
+ method public void clear();
+ method public void dump();
+ method public int keyAt(int);
+ method public void remove(int);
+ method public int size();
+ method public androidx.constraintlayout.core.motion.CustomAttribute! valueAt(int);
+ }
+
+ public static class KeyFrameArray.CustomVar {
+ ctor public KeyFrameArray.CustomVar();
+ method public void append(int, androidx.constraintlayout.core.motion.CustomVariable!);
+ method public void clear();
+ method public void dump();
+ method public int keyAt(int);
+ method public void remove(int);
+ method public int size();
+ method public androidx.constraintlayout.core.motion.CustomVariable! valueAt(int);
+ }
+
+ public class LinearCurveFit extends androidx.constraintlayout.core.motion.utils.CurveFit {
+ ctor public LinearCurveFit(double[]!, double[]![]!);
+ method public void getPos(double, double[]!);
+ method public void getPos(double, float[]!);
+ method public double getPos(double, int);
+ method public void getSlope(double, double[]!);
+ method public double getSlope(double, int);
+ method public double[]! getTimePoints();
+ }
+
+ public class MonotonicCurveFit extends androidx.constraintlayout.core.motion.utils.CurveFit {
+ ctor public MonotonicCurveFit(double[]!, double[]![]!);
+ method public static androidx.constraintlayout.core.motion.utils.MonotonicCurveFit! buildWave(String!);
+ method public void getPos(double, double[]!);
+ method public void getPos(double, float[]!);
+ method public double getPos(double, int);
+ method public void getSlope(double, double[]!);
+ method public double getSlope(double, int);
+ method public double[]! getTimePoints();
+ }
+
+ public class Oscillator {
+ ctor public Oscillator();
+ method public void addPoint(double, float);
+ method public double getSlope(double, double, double);
+ method public double getValue(double, double);
+ method public void normalize();
+ method public void setType(int, String!);
+ field public static final int BOUNCE = 6; // 0x6
+ field public static final int COS_WAVE = 5; // 0x5
+ field public static final int CUSTOM = 7; // 0x7
+ field public static final int REVERSE_SAW_WAVE = 4; // 0x4
+ field public static final int SAW_WAVE = 3; // 0x3
+ field public static final int SIN_WAVE = 0; // 0x0
+ field public static final int SQUARE_WAVE = 1; // 0x1
+ field public static String! TAG;
+ field public static final int TRIANGLE_WAVE = 2; // 0x2
+ }
+
+ public class Rect {
+ ctor public Rect();
+ method public int height();
+ method public int width();
+ field public int bottom;
+ field public int left;
+ field public int right;
+ field public int top;
+ }
+
+ public class Schlick extends androidx.constraintlayout.core.motion.utils.Easing {
+ }
+
+ public abstract class SplineSet {
+ ctor public SplineSet();
+ method public float get(float);
+ method public androidx.constraintlayout.core.motion.utils.CurveFit! getCurveFit();
+ method public float getSlope(float);
+ method public static androidx.constraintlayout.core.motion.utils.SplineSet! makeCustomSpline(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomArray!);
+ method public static androidx.constraintlayout.core.motion.utils.SplineSet! makeCustomSplineSet(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomVar!);
+ method public static androidx.constraintlayout.core.motion.utils.SplineSet! makeSpline(String!, long);
+ method public void setPoint(int, float);
+ method public void setProperty(androidx.constraintlayout.core.motion.utils.TypedValues!, float);
+ method public void setType(String!);
+ method public void setup(int);
+ field protected androidx.constraintlayout.core.motion.utils.CurveFit! mCurveFit;
+ field protected int[]! mTimePoints;
+ field protected float[]! mValues;
+ }
+
+ public static class SplineSet.CustomSet extends androidx.constraintlayout.core.motion.utils.SplineSet {
+ ctor public SplineSet.CustomSet(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomArray!);
+ method public void setPoint(int, androidx.constraintlayout.core.motion.CustomAttribute!);
+ method public void setProperty(androidx.constraintlayout.core.state.WidgetFrame!, float);
+ }
+
+ public static class SplineSet.CustomSpline extends androidx.constraintlayout.core.motion.utils.SplineSet {
+ ctor public SplineSet.CustomSpline(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomVar!);
+ method public void setPoint(int, androidx.constraintlayout.core.motion.CustomVariable!);
+ method public void setProperty(androidx.constraintlayout.core.motion.MotionWidget!, float);
+ }
+
+ public class SpringStopEngine implements androidx.constraintlayout.core.motion.utils.StopEngine {
+ ctor public SpringStopEngine();
+ method public String! debug(String!, float);
+ method public float getAcceleration();
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ method public float getVelocity(float);
+ method public boolean isStopped();
+ method public void springConfig(float, float, float, float, float, float, float, int);
+ }
+
+ public class StepCurve extends androidx.constraintlayout.core.motion.utils.Easing {
+ }
+
+ public interface StopEngine {
+ method public String! debug(String!, float);
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ method public float getVelocity(float);
+ method public boolean isStopped();
+ }
+
+ public class StopLogicEngine implements androidx.constraintlayout.core.motion.utils.StopEngine {
+ ctor public StopLogicEngine();
+ method public void config(float, float, float, float, float, float);
+ method public String! debug(String!, float);
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ method public float getVelocity(float);
+ method public boolean isStopped();
+ }
+
+ public static class StopLogicEngine.Decelerate implements androidx.constraintlayout.core.motion.utils.StopEngine {
+ ctor public StopLogicEngine.Decelerate();
+ method public void config(float, float, float);
+ method public String! debug(String!, float);
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ method public float getVelocity(float);
+ method public boolean isStopped();
+ }
+
+ public abstract class TimeCycleSplineSet {
+ ctor public TimeCycleSplineSet();
+ method protected float calcWave(float);
+ method public androidx.constraintlayout.core.motion.utils.CurveFit! getCurveFit();
+ method public void setPoint(int, float, float, int, float);
+ method protected void setStartTime(long);
+ method public void setType(String!);
+ method public void setup(int);
+ field protected static final int CURVE_OFFSET = 2; // 0x2
+ field protected static final int CURVE_PERIOD = 1; // 0x1
+ field protected static final int CURVE_VALUE = 0; // 0x0
+ field protected float[]! mCache;
+ field protected boolean mContinue;
+ field protected int mCount;
+ field protected androidx.constraintlayout.core.motion.utils.CurveFit! mCurveFit;
+ field protected float mLastCycle;
+ field protected long mLastTime;
+ field protected int[]! mTimePoints;
+ field protected String! mType;
+ field protected float[]![]! mValues;
+ field protected int mWaveShape;
+ field protected static float sVal2PI;
+ }
+
+ public static class TimeCycleSplineSet.CustomSet extends androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet {
+ ctor public TimeCycleSplineSet.CustomSet(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomArray!);
+ method public void setPoint(int, androidx.constraintlayout.core.motion.CustomAttribute!, float, int, float);
+ method public boolean setProperty(androidx.constraintlayout.core.motion.MotionWidget!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ }
+
+ public static class TimeCycleSplineSet.CustomVarSet extends androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet {
+ ctor public TimeCycleSplineSet.CustomVarSet(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomVar!);
+ method public void setPoint(int, androidx.constraintlayout.core.motion.CustomVariable!, float, int, float);
+ method public boolean setProperty(androidx.constraintlayout.core.motion.MotionWidget!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ }
+
+ protected static class TimeCycleSplineSet.Sort {
+ ctor protected TimeCycleSplineSet.Sort();
+ }
+
+ public class TypedBundle {
+ ctor public TypedBundle();
+ method public void add(int, boolean);
+ method public void add(int, float);
+ method public void add(int, int);
+ method public void add(int, String!);
+ method public void addIfNotNull(int, String!);
+ method public void applyDelta(androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void applyDelta(androidx.constraintlayout.core.motion.utils.TypedValues!);
+ method public void clear();
+ method public int getInteger(int);
+ }
+
+ public interface TypedValues {
+ method public int getId(String!);
+ method public boolean setValue(int, boolean);
+ method public boolean setValue(int, float);
+ method public boolean setValue(int, int);
+ method public boolean setValue(int, String!);
+ field public static final int BOOLEAN_MASK = 1; // 0x1
+ field public static final int FLOAT_MASK = 4; // 0x4
+ field public static final int INT_MASK = 2; // 0x2
+ field public static final int STRING_MASK = 8; // 0x8
+ field public static final String S_CUSTOM = "CUSTOM";
+ field public static final int TYPE_FRAME_POSITION = 100; // 0x64
+ field public static final int TYPE_TARGET = 101; // 0x65
+ }
+
+ public static interface TypedValues.AttributesType {
+ method public static int getId(String!);
+ method public static int getType(int);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "KeyAttributes";
+ field public static final String S_ALPHA = "alpha";
+ field public static final String S_CURVE_FIT = "curveFit";
+ field public static final String S_CUSTOM = "CUSTOM";
+ field public static final String S_EASING = "easing";
+ field public static final String S_ELEVATION = "elevation";
+ field public static final String S_FRAME = "frame";
+ field public static final String S_PATH_ROTATE = "pathRotate";
+ field public static final String S_PIVOT_TARGET = "pivotTarget";
+ field public static final String S_PIVOT_X = "pivotX";
+ field public static final String S_PIVOT_Y = "pivotY";
+ field public static final String S_PROGRESS = "progress";
+ field public static final String S_ROTATION_X = "rotationX";
+ field public static final String S_ROTATION_Y = "rotationY";
+ field public static final String S_ROTATION_Z = "rotationZ";
+ field public static final String S_SCALE_X = "scaleX";
+ field public static final String S_SCALE_Y = "scaleY";
+ field public static final String S_TARGET = "target";
+ field public static final String S_TRANSLATION_X = "translationX";
+ field public static final String S_TRANSLATION_Y = "translationY";
+ field public static final String S_TRANSLATION_Z = "translationZ";
+ field public static final String S_VISIBILITY = "visibility";
+ field public static final int TYPE_ALPHA = 303; // 0x12f
+ field public static final int TYPE_CURVE_FIT = 301; // 0x12d
+ field public static final int TYPE_EASING = 317; // 0x13d
+ field public static final int TYPE_ELEVATION = 307; // 0x133
+ field public static final int TYPE_PATH_ROTATE = 316; // 0x13c
+ field public static final int TYPE_PIVOT_TARGET = 318; // 0x13e
+ field public static final int TYPE_PIVOT_X = 313; // 0x139
+ field public static final int TYPE_PIVOT_Y = 314; // 0x13a
+ field public static final int TYPE_PROGRESS = 315; // 0x13b
+ field public static final int TYPE_ROTATION_X = 308; // 0x134
+ field public static final int TYPE_ROTATION_Y = 309; // 0x135
+ field public static final int TYPE_ROTATION_Z = 310; // 0x136
+ field public static final int TYPE_SCALE_X = 311; // 0x137
+ field public static final int TYPE_SCALE_Y = 312; // 0x138
+ field public static final int TYPE_TRANSLATION_X = 304; // 0x130
+ field public static final int TYPE_TRANSLATION_Y = 305; // 0x131
+ field public static final int TYPE_TRANSLATION_Z = 306; // 0x132
+ field public static final int TYPE_VISIBILITY = 302; // 0x12e
+ }
+
+ public static interface TypedValues.Custom {
+ method public static int getId(String!);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "Custom";
+ field public static final String S_BOOLEAN = "boolean";
+ field public static final String S_COLOR = "color";
+ field public static final String S_DIMENSION = "dimension";
+ field public static final String S_FLOAT = "float";
+ field public static final String S_INT = "integer";
+ field public static final String S_REFERENCE = "reference";
+ field public static final String S_STRING = "string";
+ field public static final int TYPE_BOOLEAN = 904; // 0x388
+ field public static final int TYPE_COLOR = 902; // 0x386
+ field public static final int TYPE_DIMENSION = 905; // 0x389
+ field public static final int TYPE_FLOAT = 901; // 0x385
+ field public static final int TYPE_INT = 900; // 0x384
+ field public static final int TYPE_REFERENCE = 906; // 0x38a
+ field public static final int TYPE_STRING = 903; // 0x387
+ }
+
+ public static interface TypedValues.CycleType {
+ method public static int getId(String!);
+ method public static int getType(int);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "KeyCycle";
+ field public static final String S_ALPHA = "alpha";
+ field public static final String S_CURVE_FIT = "curveFit";
+ field public static final String S_CUSTOM_WAVE_SHAPE = "customWave";
+ field public static final String S_EASING = "easing";
+ field public static final String S_ELEVATION = "elevation";
+ field public static final String S_PATH_ROTATE = "pathRotate";
+ field public static final String S_PIVOT_X = "pivotX";
+ field public static final String S_PIVOT_Y = "pivotY";
+ field public static final String S_PROGRESS = "progress";
+ field public static final String S_ROTATION_X = "rotationX";
+ field public static final String S_ROTATION_Y = "rotationY";
+ field public static final String S_ROTATION_Z = "rotationZ";
+ field public static final String S_SCALE_X = "scaleX";
+ field public static final String S_SCALE_Y = "scaleY";
+ field public static final String S_TRANSLATION_X = "translationX";
+ field public static final String S_TRANSLATION_Y = "translationY";
+ field public static final String S_TRANSLATION_Z = "translationZ";
+ field public static final String S_VISIBILITY = "visibility";
+ field public static final String S_WAVE_OFFSET = "offset";
+ field public static final String S_WAVE_PERIOD = "period";
+ field public static final String S_WAVE_PHASE = "phase";
+ field public static final String S_WAVE_SHAPE = "waveShape";
+ field public static final int TYPE_ALPHA = 403; // 0x193
+ field public static final int TYPE_CURVE_FIT = 401; // 0x191
+ field public static final int TYPE_CUSTOM_WAVE_SHAPE = 422; // 0x1a6
+ field public static final int TYPE_EASING = 420; // 0x1a4
+ field public static final int TYPE_ELEVATION = 307; // 0x133
+ field public static final int TYPE_PATH_ROTATE = 416; // 0x1a0
+ field public static final int TYPE_PIVOT_X = 313; // 0x139
+ field public static final int TYPE_PIVOT_Y = 314; // 0x13a
+ field public static final int TYPE_PROGRESS = 315; // 0x13b
+ field public static final int TYPE_ROTATION_X = 308; // 0x134
+ field public static final int TYPE_ROTATION_Y = 309; // 0x135
+ field public static final int TYPE_ROTATION_Z = 310; // 0x136
+ field public static final int TYPE_SCALE_X = 311; // 0x137
+ field public static final int TYPE_SCALE_Y = 312; // 0x138
+ field public static final int TYPE_TRANSLATION_X = 304; // 0x130
+ field public static final int TYPE_TRANSLATION_Y = 305; // 0x131
+ field public static final int TYPE_TRANSLATION_Z = 306; // 0x132
+ field public static final int TYPE_VISIBILITY = 402; // 0x192
+ field public static final int TYPE_WAVE_OFFSET = 424; // 0x1a8
+ field public static final int TYPE_WAVE_PERIOD = 423; // 0x1a7
+ field public static final int TYPE_WAVE_PHASE = 425; // 0x1a9
+ field public static final int TYPE_WAVE_SHAPE = 421; // 0x1a5
+ }
+
+ public static interface TypedValues.MotionScene {
+ method public static int getId(String!);
+ method public static int getType(int);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "MotionScene";
+ field public static final String S_DEFAULT_DURATION = "defaultDuration";
+ field public static final String S_LAYOUT_DURING_TRANSITION = "layoutDuringTransition";
+ field public static final int TYPE_DEFAULT_DURATION = 600; // 0x258
+ field public static final int TYPE_LAYOUT_DURING_TRANSITION = 601; // 0x259
+ }
+
+ public static interface TypedValues.MotionType {
+ method public static int getId(String!);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "Motion";
+ field public static final String S_ANIMATE_CIRCLEANGLE_TO = "AnimateCircleAngleTo";
+ field public static final String S_ANIMATE_RELATIVE_TO = "AnimateRelativeTo";
+ field public static final String S_DRAW_PATH = "DrawPath";
+ field public static final String S_EASING = "TransitionEasing";
+ field public static final String S_PATHMOTION_ARC = "PathMotionArc";
+ field public static final String S_PATH_ROTATE = "PathRotate";
+ field public static final String S_POLAR_RELATIVETO = "PolarRelativeTo";
+ field public static final String S_QUANTIZE_INTERPOLATOR = "QuantizeInterpolator";
+ field public static final String S_QUANTIZE_INTERPOLATOR_ID = "QuantizeInterpolatorID";
+ field public static final String S_QUANTIZE_INTERPOLATOR_TYPE = "QuantizeInterpolatorType";
+ field public static final String S_QUANTIZE_MOTIONSTEPS = "QuantizeMotionSteps";
+ field public static final String S_QUANTIZE_MOTION_PHASE = "QuantizeMotionPhase";
+ field public static final String S_STAGGER = "Stagger";
+ field public static final int TYPE_ANIMATE_CIRCLEANGLE_TO = 606; // 0x25e
+ field public static final int TYPE_ANIMATE_RELATIVE_TO = 605; // 0x25d
+ field public static final int TYPE_DRAW_PATH = 608; // 0x260
+ field public static final int TYPE_EASING = 603; // 0x25b
+ field public static final int TYPE_PATHMOTION_ARC = 607; // 0x25f
+ field public static final int TYPE_PATH_ROTATE = 601; // 0x259
+ field public static final int TYPE_POLAR_RELATIVETO = 609; // 0x261
+ field public static final int TYPE_QUANTIZE_INTERPOLATOR = 604; // 0x25c
+ field public static final int TYPE_QUANTIZE_INTERPOLATOR_ID = 612; // 0x264
+ field public static final int TYPE_QUANTIZE_INTERPOLATOR_TYPE = 611; // 0x263
+ field public static final int TYPE_QUANTIZE_MOTIONSTEPS = 610; // 0x262
+ field public static final int TYPE_QUANTIZE_MOTION_PHASE = 602; // 0x25a
+ field public static final int TYPE_STAGGER = 600; // 0x258
+ }
+
+ public static interface TypedValues.OnSwipe {
+ field public static final String AUTOCOMPLETE_MODE = "autocompletemode";
+ field public static final String![]! AUTOCOMPLETE_MODE_ENUM;
+ field public static final String DRAG_DIRECTION = "dragdirection";
+ field public static final String DRAG_SCALE = "dragscale";
+ field public static final String DRAG_THRESHOLD = "dragthreshold";
+ field public static final String LIMIT_BOUNDS_TO = "limitboundsto";
+ field public static final String MAX_ACCELERATION = "maxacceleration";
+ field public static final String MAX_VELOCITY = "maxvelocity";
+ field public static final String MOVE_WHEN_SCROLLAT_TOP = "movewhenscrollattop";
+ field public static final String NESTED_SCROLL_FLAGS = "nestedscrollflags";
+ field public static final String![]! NESTED_SCROLL_FLAGS_ENUM;
+ field public static final String ON_TOUCH_UP = "ontouchup";
+ field public static final String![]! ON_TOUCH_UP_ENUM;
+ field public static final String ROTATION_CENTER_ID = "rotationcenterid";
+ field public static final String SPRINGS_TOP_THRESHOLD = "springstopthreshold";
+ field public static final String SPRING_BOUNDARY = "springboundary";
+ field public static final String![]! SPRING_BOUNDARY_ENUM;
+ field public static final String SPRING_DAMPING = "springdamping";
+ field public static final String SPRING_MASS = "springmass";
+ field public static final String SPRING_STIFFNESS = "springstiffness";
+ field public static final String TOUCH_ANCHOR_ID = "touchanchorid";
+ field public static final String TOUCH_ANCHOR_SIDE = "touchanchorside";
+ field public static final String TOUCH_REGION_ID = "touchregionid";
+ }
+
+ public static interface TypedValues.PositionType {
+ method public static int getId(String!);
+ method public static int getType(int);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "KeyPosition";
+ field public static final String S_DRAWPATH = "drawPath";
+ field public static final String S_PERCENT_HEIGHT = "percentHeight";
+ field public static final String S_PERCENT_WIDTH = "percentWidth";
+ field public static final String S_PERCENT_X = "percentX";
+ field public static final String S_PERCENT_Y = "percentY";
+ field public static final String S_SIZE_PERCENT = "sizePercent";
+ field public static final String S_TRANSITION_EASING = "transitionEasing";
+ field public static final int TYPE_CURVE_FIT = 508; // 0x1fc
+ field public static final int TYPE_DRAWPATH = 502; // 0x1f6
+ field public static final int TYPE_PATH_MOTION_ARC = 509; // 0x1fd
+ field public static final int TYPE_PERCENT_HEIGHT = 504; // 0x1f8
+ field public static final int TYPE_PERCENT_WIDTH = 503; // 0x1f7
+ field public static final int TYPE_PERCENT_X = 506; // 0x1fa
+ field public static final int TYPE_PERCENT_Y = 507; // 0x1fb
+ field public static final int TYPE_POSITION_TYPE = 510; // 0x1fe
+ field public static final int TYPE_SIZE_PERCENT = 505; // 0x1f9
+ field public static final int TYPE_TRANSITION_EASING = 501; // 0x1f5
+ }
+
+ public static interface TypedValues.TransitionType {
+ method public static int getId(String!);
+ method public static int getType(int);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "Transitions";
+ field public static final String S_AUTO_TRANSITION = "autoTransition";
+ field public static final String S_DURATION = "duration";
+ field public static final String S_FROM = "from";
+ field public static final String S_INTERPOLATOR = "motionInterpolator";
+ field public static final String S_PATH_MOTION_ARC = "pathMotionArc";
+ field public static final String S_STAGGERED = "staggered";
+ field public static final String S_TO = "to";
+ field public static final String S_TRANSITION_FLAGS = "transitionFlags";
+ field public static final int TYPE_AUTO_TRANSITION = 704; // 0x2c0
+ field public static final int TYPE_DURATION = 700; // 0x2bc
+ field public static final int TYPE_FROM = 701; // 0x2bd
+ field public static final int TYPE_INTERPOLATOR = 705; // 0x2c1
+ field public static final int TYPE_PATH_MOTION_ARC = 509; // 0x1fd
+ field public static final int TYPE_STAGGERED = 706; // 0x2c2
+ field public static final int TYPE_TO = 702; // 0x2be
+ field public static final int TYPE_TRANSITION_FLAGS = 707; // 0x2c3
+ }
+
+ public static interface TypedValues.TriggerType {
+ method public static int getId(String!);
+ field public static final String CROSS = "CROSS";
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "KeyTrigger";
+ field public static final String NEGATIVE_CROSS = "negativeCross";
+ field public static final String POSITIVE_CROSS = "positiveCross";
+ field public static final String POST_LAYOUT = "postLayout";
+ field public static final String TRIGGER_COLLISION_ID = "triggerCollisionId";
+ field public static final String TRIGGER_COLLISION_VIEW = "triggerCollisionView";
+ field public static final String TRIGGER_ID = "triggerID";
+ field public static final String TRIGGER_RECEIVER = "triggerReceiver";
+ field public static final String TRIGGER_SLACK = "triggerSlack";
+ field public static final int TYPE_CROSS = 312; // 0x138
+ field public static final int TYPE_NEGATIVE_CROSS = 310; // 0x136
+ field public static final int TYPE_POSITIVE_CROSS = 309; // 0x135
+ field public static final int TYPE_POST_LAYOUT = 304; // 0x130
+ field public static final int TYPE_TRIGGER_COLLISION_ID = 307; // 0x133
+ field public static final int TYPE_TRIGGER_COLLISION_VIEW = 306; // 0x132
+ field public static final int TYPE_TRIGGER_ID = 308; // 0x134
+ field public static final int TYPE_TRIGGER_RECEIVER = 311; // 0x137
+ field public static final int TYPE_TRIGGER_SLACK = 305; // 0x131
+ field public static final int TYPE_VIEW_TRANSITION_ON_CROSS = 301; // 0x12d
+ field public static final int TYPE_VIEW_TRANSITION_ON_NEGATIVE_CROSS = 303; // 0x12f
+ field public static final int TYPE_VIEW_TRANSITION_ON_POSITIVE_CROSS = 302; // 0x12e
+ field public static final String VIEW_TRANSITION_ON_CROSS = "viewTransitionOnCross";
+ field public static final String VIEW_TRANSITION_ON_NEGATIVE_CROSS = "viewTransitionOnNegativeCross";
+ field public static final String VIEW_TRANSITION_ON_POSITIVE_CROSS = "viewTransitionOnPositiveCross";
+ }
+
+ public class Utils {
+ ctor public Utils();
+ method public int getInterpolatedColor(float[]!);
+ method public static void log(String!);
+ method public static void log(String!, String!);
+ method public static void logStack(String!, int);
+ method public static void loge(String!, String!);
+ method public static int rgbaTocColor(float, float, float, float);
+ method public static void setDebugHandle(androidx.constraintlayout.core.motion.utils.Utils.DebugHandle!);
+ method public static void socketSend(String!);
+ }
+
+ public static interface Utils.DebugHandle {
+ method public void message(String!);
+ }
+
+ public class VelocityMatrix {
+ ctor public VelocityMatrix();
+ method public void applyTransform(float, float, int, int, float[]!);
+ method public void clear();
+ method public void setRotationVelocity(androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!, float);
+ method public void setRotationVelocity(androidx.constraintlayout.core.motion.utils.SplineSet!, float);
+ method public void setScaleVelocity(androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!, androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!, float);
+ method public void setScaleVelocity(androidx.constraintlayout.core.motion.utils.SplineSet!, androidx.constraintlayout.core.motion.utils.SplineSet!, float);
+ method public void setTranslationVelocity(androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!, androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!, float);
+ method public void setTranslationVelocity(androidx.constraintlayout.core.motion.utils.SplineSet!, androidx.constraintlayout.core.motion.utils.SplineSet!, float);
+ }
+
+ public class ViewState {
+ ctor public ViewState();
+ method public void getState(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public int height();
+ method public int width();
+ field public int bottom;
+ field public int left;
+ field public int right;
+ field public float rotation;
+ field public int top;
+ }
+
+}
+
+package androidx.constraintlayout.core.parser {
+
+ public class CLArray extends androidx.constraintlayout.core.parser.CLContainer {
+ ctor public CLArray(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ }
+
+ public class CLContainer extends androidx.constraintlayout.core.parser.CLElement {
+ ctor public CLContainer(char[]!);
+ method public void add(androidx.constraintlayout.core.parser.CLElement!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ method public void clear();
+ method public androidx.constraintlayout.core.parser.CLContainer clone();
+ method public androidx.constraintlayout.core.parser.CLElement! get(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLElement! get(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLArray! getArray(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLArray! getArray(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLArray! getArrayOrCreate(String!);
+ method public androidx.constraintlayout.core.parser.CLArray! getArrayOrNull(String!);
+ method public boolean getBoolean(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public boolean getBoolean(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public float getFloat(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public float getFloat(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public float getFloatOrNaN(String!);
+ method public int getInt(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public int getInt(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLObject! getObject(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLObject! getObject(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLObject! getObjectOrNull(String!);
+ method public androidx.constraintlayout.core.parser.CLElement! getOrNull(int);
+ method public androidx.constraintlayout.core.parser.CLElement! getOrNull(String!);
+ method public String! getString(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public String! getString(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public String! getStringOrNull(int);
+ method public String! getStringOrNull(String!);
+ method public boolean has(String!);
+ method public java.util.ArrayList<java.lang.String!>! names();
+ method public void put(String!, androidx.constraintlayout.core.parser.CLElement!);
+ method public void putNumber(String!, float);
+ method public void putString(String!, String!);
+ method public void remove(String!);
+ method public int size();
+ }
+
+ public class CLElement implements java.lang.Cloneable {
+ ctor public CLElement(char[]!);
+ method protected void addIndent(StringBuilder!, int);
+ method public androidx.constraintlayout.core.parser.CLElement clone();
+ method public String! content();
+ method public androidx.constraintlayout.core.parser.CLElement! getContainer();
+ method protected String! getDebugName();
+ method public long getEnd();
+ method public float getFloat();
+ method public int getInt();
+ method public int getLine();
+ method public long getStart();
+ method protected String! getStrClass();
+ method public boolean hasContent();
+ method public boolean isDone();
+ method public boolean isStarted();
+ method public boolean notStarted();
+ method public void setContainer(androidx.constraintlayout.core.parser.CLContainer!);
+ method public void setEnd(long);
+ method public void setLine(int);
+ method public void setStart(long);
+ method protected String! toFormattedJSON(int, int);
+ method protected String! toJSON();
+ field protected androidx.constraintlayout.core.parser.CLContainer! mContainer;
+ field protected long mEnd;
+ field protected long mStart;
+ field protected static int sBaseIndent;
+ field protected static int sMaxLine;
+ }
+
+ public class CLKey extends androidx.constraintlayout.core.parser.CLContainer {
+ ctor public CLKey(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(String!, androidx.constraintlayout.core.parser.CLElement!);
+ method public String! getName();
+ method public androidx.constraintlayout.core.parser.CLElement! getValue();
+ method public void set(androidx.constraintlayout.core.parser.CLElement!);
+ }
+
+ public class CLNumber extends androidx.constraintlayout.core.parser.CLElement {
+ ctor public CLNumber(char[]!);
+ ctor public CLNumber(float);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ method public boolean isInt();
+ method public void putValue(float);
+ }
+
+ public class CLObject extends androidx.constraintlayout.core.parser.CLContainer implements java.lang.Iterable<androidx.constraintlayout.core.parser.CLKey!> {
+ ctor public CLObject(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLObject! allocate(char[]!);
+ method public androidx.constraintlayout.core.parser.CLObject clone();
+ method public java.util.Iterator<androidx.constraintlayout.core.parser.CLKey!>! iterator();
+ method public String! toFormattedJSON();
+ method public String! toFormattedJSON(int, int);
+ method public String! toJSON();
+ }
+
+ public class CLParser {
+ ctor public CLParser(String!);
+ method public androidx.constraintlayout.core.parser.CLObject! parse() throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public static androidx.constraintlayout.core.parser.CLObject! parse(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ }
+
+ public class CLParsingException extends java.lang.Exception {
+ ctor public CLParsingException(String!, androidx.constraintlayout.core.parser.CLElement!);
+ method public String! reason();
+ }
+
+ public class CLString extends androidx.constraintlayout.core.parser.CLElement {
+ ctor public CLString(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLString from(String);
+ }
+
+ public class CLToken extends androidx.constraintlayout.core.parser.CLElement {
+ ctor public CLToken(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ method public boolean getBoolean() throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLToken.Type! getType();
+ method public boolean isNull() throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public boolean validate(char, long);
+ }
+
+}
+
+package androidx.constraintlayout.core.state {
+
+ public class ConstraintReference implements androidx.constraintlayout.core.state.Reference {
+ ctor public ConstraintReference(androidx.constraintlayout.core.state.State!);
+ method public void addCustomColor(String!, int);
+ method public void addCustomFloat(String!, float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! alpha(float);
+ method public void apply();
+ method public void applyWidgetConstraints();
+ method public androidx.constraintlayout.core.state.ConstraintReference! baseline();
+ method public androidx.constraintlayout.core.state.ConstraintReference! baselineToBaseline(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! baselineToBottom(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! baselineToTop(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! bias(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! bottom();
+ method public androidx.constraintlayout.core.state.ConstraintReference! bottomToBottom(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! bottomToTop(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! centerHorizontally(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! centerVertically(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! circularConstraint(Object!, float, float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! clear();
+ method public androidx.constraintlayout.core.state.ConstraintReference! clearAll();
+ method public androidx.constraintlayout.core.state.ConstraintReference! clearHorizontal();
+ method public androidx.constraintlayout.core.state.ConstraintReference! clearVertical();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! createConstraintWidget();
+ method public androidx.constraintlayout.core.state.ConstraintReference! end();
+ method public androidx.constraintlayout.core.state.ConstraintReference! endToEnd(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! endToStart(Object!);
+ method public float getAlpha();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getConstraintWidget();
+ method public androidx.constraintlayout.core.state.helpers.Facade! getFacade();
+ method public androidx.constraintlayout.core.state.Dimension! getHeight();
+ method public int getHorizontalChainStyle();
+ method public float getHorizontalChainWeight();
+ method public Object! getKey();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getRotationZ();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public String! getTag();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public int getVerticalChainStyle(int);
+ method public float getVerticalChainWeight();
+ method public Object! getView();
+ method public androidx.constraintlayout.core.state.Dimension! getWidth();
+ method public androidx.constraintlayout.core.state.ConstraintReference! height(androidx.constraintlayout.core.state.Dimension!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! horizontalBias(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! left();
+ method public androidx.constraintlayout.core.state.ConstraintReference! leftToLeft(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! leftToRight(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! margin(int);
+ method public androidx.constraintlayout.core.state.ConstraintReference! margin(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! marginGone(int);
+ method public androidx.constraintlayout.core.state.ConstraintReference! marginGone(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! pivotX(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! pivotY(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! right();
+ method public androidx.constraintlayout.core.state.ConstraintReference! rightToLeft(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! rightToRight(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! rotationX(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! rotationY(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! rotationZ(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! scaleX(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! scaleY(float);
+ method public void setConstraintWidget(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void setFacade(androidx.constraintlayout.core.state.helpers.Facade!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! setHeight(androidx.constraintlayout.core.state.Dimension!);
+ method public void setHorizontalChainStyle(int);
+ method public void setHorizontalChainWeight(float);
+ method public void setKey(Object!);
+ method public void setTag(String!);
+ method public void setVerticalChainStyle(int);
+ method public void setVerticalChainWeight(float);
+ method public void setView(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! setWidth(androidx.constraintlayout.core.state.Dimension!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! start();
+ method public androidx.constraintlayout.core.state.ConstraintReference! startToEnd(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! startToStart(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! top();
+ method public androidx.constraintlayout.core.state.ConstraintReference! topToBottom(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! topToTop(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! translationX(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! translationY(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! translationZ(float);
+ method public void validate() throws java.lang.Exception;
+ method public androidx.constraintlayout.core.state.ConstraintReference! verticalBias(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! visibility(int);
+ method public androidx.constraintlayout.core.state.ConstraintReference! width(androidx.constraintlayout.core.state.Dimension!);
+ field protected Object! mBottomToBottom;
+ field protected Object! mBottomToTop;
+ field protected Object! mEndToEnd;
+ field protected Object! mEndToStart;
+ field protected float mHorizontalBias;
+ field protected Object! mLeftToLeft;
+ field protected Object! mLeftToRight;
+ field protected int mMarginBottom;
+ field protected int mMarginBottomGone;
+ field protected int mMarginEnd;
+ field protected int mMarginEndGone;
+ field protected int mMarginLeft;
+ field protected int mMarginLeftGone;
+ field protected int mMarginRight;
+ field protected int mMarginRightGone;
+ field protected int mMarginStart;
+ field protected int mMarginStartGone;
+ field protected int mMarginTop;
+ field protected int mMarginTopGone;
+ field protected Object! mRightToLeft;
+ field protected Object! mRightToRight;
+ field protected Object! mStartToEnd;
+ field protected Object! mStartToStart;
+ field protected Object! mTopToBottom;
+ field protected Object! mTopToTop;
+ field protected float mVerticalBias;
+ }
+
+ public static interface ConstraintReference.ConstraintReferenceFactory {
+ method public androidx.constraintlayout.core.state.ConstraintReference! create(androidx.constraintlayout.core.state.State!);
+ }
+
+ public class ConstraintSetParser {
+ ctor public ConstraintSetParser();
+ method public static void parseDesignElementsJSON(String!, java.util.ArrayList<androidx.constraintlayout.core.state.ConstraintSetParser.DesignElement!>!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public static void parseJSON(String!, androidx.constraintlayout.core.state.State!, androidx.constraintlayout.core.state.ConstraintSetParser.LayoutVariables!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public static void parseJSON(String!, androidx.constraintlayout.core.state.Transition!, int);
+ method public static void parseMotionSceneJSON(androidx.constraintlayout.core.state.CoreMotionScene!, String!);
+ }
+
+ public static class ConstraintSetParser.DesignElement {
+ method public String! getId();
+ method public java.util.HashMap<java.lang.String!,java.lang.String!>! getParams();
+ method public String! getType();
+ }
+
+ public static class ConstraintSetParser.LayoutVariables {
+ ctor public ConstraintSetParser.LayoutVariables();
+ method public void putOverride(String!, float);
+ }
+
+ public enum ConstraintSetParser.MotionLayoutDebugFlags {
+ enum_constant public static final androidx.constraintlayout.core.state.ConstraintSetParser.MotionLayoutDebugFlags NONE;
+ enum_constant public static final androidx.constraintlayout.core.state.ConstraintSetParser.MotionLayoutDebugFlags SHOW_ALL;
+ enum_constant public static final androidx.constraintlayout.core.state.ConstraintSetParser.MotionLayoutDebugFlags UNKNOWN;
+ }
+
+ public interface CoreMotionScene {
+ method public String! getConstraintSet(int);
+ method public String! getConstraintSet(String!);
+ method public String! getTransition(String!);
+ method public void setConstraintSetContent(String!, String!);
+ method public void setDebugName(String!);
+ method public void setTransitionContent(String!, String!);
+ }
+
+ public interface CorePixelDp {
+ method public float toPixels(float);
+ }
+
+ public class Dimension {
+ method public void apply(androidx.constraintlayout.core.state.State!, androidx.constraintlayout.core.widgets.ConstraintWidget!, int);
+ method public static androidx.constraintlayout.core.state.Dimension! createFixed(int);
+ method public static androidx.constraintlayout.core.state.Dimension! createFixed(Object!);
+ method public static androidx.constraintlayout.core.state.Dimension! createParent();
+ method public static androidx.constraintlayout.core.state.Dimension! createPercent(Object!, float);
+ method public static androidx.constraintlayout.core.state.Dimension! createRatio(String!);
+ method public static androidx.constraintlayout.core.state.Dimension! createSpread();
+ method public static androidx.constraintlayout.core.state.Dimension! createSuggested(int);
+ method public static androidx.constraintlayout.core.state.Dimension! createSuggested(Object!);
+ method public static androidx.constraintlayout.core.state.Dimension! createWrap();
+ method public boolean equalsFixedValue(int);
+ method public androidx.constraintlayout.core.state.Dimension! fixed(int);
+ method public androidx.constraintlayout.core.state.Dimension! fixed(Object!);
+ method public androidx.constraintlayout.core.state.Dimension! max(int);
+ method public androidx.constraintlayout.core.state.Dimension! max(Object!);
+ method public androidx.constraintlayout.core.state.Dimension! min(int);
+ method public androidx.constraintlayout.core.state.Dimension! min(Object!);
+ method public androidx.constraintlayout.core.state.Dimension! percent(Object!, float);
+ method public androidx.constraintlayout.core.state.Dimension! ratio(String!);
+ method public androidx.constraintlayout.core.state.Dimension! suggested(int);
+ method public androidx.constraintlayout.core.state.Dimension! suggested(Object!);
+ field public static final Object! FIXED_DIMENSION;
+ field public static final Object! PARENT_DIMENSION;
+ field public static final Object! PERCENT_DIMENSION;
+ field public static final Object! RATIO_DIMENSION;
+ field public static final Object! SPREAD_DIMENSION;
+ field public static final Object! WRAP_DIMENSION;
+ }
+
+ public enum Dimension.Type {
+ enum_constant public static final androidx.constraintlayout.core.state.Dimension.Type FIXED;
+ enum_constant public static final androidx.constraintlayout.core.state.Dimension.Type MATCH_CONSTRAINT;
+ enum_constant public static final androidx.constraintlayout.core.state.Dimension.Type MATCH_PARENT;
+ enum_constant public static final androidx.constraintlayout.core.state.Dimension.Type WRAP;
+ }
+
+ public class HelperReference extends androidx.constraintlayout.core.state.ConstraintReference implements androidx.constraintlayout.core.state.helpers.Facade {
+ ctor public HelperReference(androidx.constraintlayout.core.state.State!, androidx.constraintlayout.core.state.State.Helper!);
+ method public androidx.constraintlayout.core.state.HelperReference! add(java.lang.Object!...!);
+ method public void applyBase();
+ method public androidx.constraintlayout.core.widgets.HelperWidget! getHelperWidget();
+ method public androidx.constraintlayout.core.state.State.Helper! getType();
+ method public void setHelperWidget(androidx.constraintlayout.core.widgets.HelperWidget!);
+ field protected final androidx.constraintlayout.core.state.State! mHelperState;
+ field protected java.util.ArrayList<java.lang.Object!>! mReferences;
+ }
+
+ public interface Interpolator {
+ method public float getInterpolation(float);
+ }
+
+ public interface Reference {
+ method public void apply();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getConstraintWidget();
+ method public androidx.constraintlayout.core.state.helpers.Facade! getFacade();
+ method public Object! getKey();
+ method public void setConstraintWidget(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void setKey(Object!);
+ }
+
+ public class Registry {
+ ctor public Registry();
+ method public String! currentContent(String!);
+ method public String! currentLayoutInformation(String!);
+ method public static androidx.constraintlayout.core.state.Registry! getInstance();
+ method public long getLastModified(String!);
+ method public java.util.Set<java.lang.String!>! getLayoutList();
+ method public void register(String!, androidx.constraintlayout.core.state.RegistryCallback!);
+ method public void setDrawDebug(String!, int);
+ method public void setLayoutInformationMode(String!, int);
+ method public void unregister(String!, androidx.constraintlayout.core.state.RegistryCallback!);
+ method public void updateContent(String!, String!);
+ method public void updateDimensions(String!, int, int);
+ method public void updateProgress(String!, float);
+ }
+
+ public interface RegistryCallback {
+ method public String! currentLayoutInformation();
+ method public String! currentMotionScene();
+ method public long getLastModified();
+ method public void onDimensions(int, int);
+ method public void onNewMotionScene(String!);
+ method public void onProgress(float);
+ method public void setDrawDebug(int);
+ method public void setLayoutInformationMode(int);
+ }
+
+ public class State {
+ ctor public State();
+ method public void apply(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ method public androidx.constraintlayout.core.state.helpers.BarrierReference! barrier(Object!, androidx.constraintlayout.core.state.State.Direction!);
+ method public void baselineNeededFor(Object!);
+ method public androidx.constraintlayout.core.state.helpers.AlignHorizontallyReference! centerHorizontally(java.lang.Object!...!);
+ method public androidx.constraintlayout.core.state.helpers.AlignVerticallyReference! centerVertically(java.lang.Object!...!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! constraints(Object!);
+ method public int convertDimension(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! createConstraintReference(Object!);
+ method public void directMapping();
+ method public androidx.constraintlayout.core.state.helpers.FlowReference! getFlow(Object!, boolean);
+ method public androidx.constraintlayout.core.state.helpers.GridReference getGrid(Object, String);
+ method public androidx.constraintlayout.core.state.helpers.FlowReference! getHorizontalFlow();
+ method public androidx.constraintlayout.core.state.helpers.FlowReference! getHorizontalFlow(java.lang.Object!...!);
+ method public java.util.ArrayList<java.lang.String!>! getIdsForTag(String!);
+ method public androidx.constraintlayout.core.state.helpers.FlowReference! getVerticalFlow();
+ method public androidx.constraintlayout.core.state.helpers.FlowReference! getVerticalFlow(java.lang.Object!...!);
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! guideline(Object!, int);
+ method public androidx.constraintlayout.core.state.State! height(androidx.constraintlayout.core.state.Dimension!);
+ method public androidx.constraintlayout.core.state.HelperReference! helper(Object!, androidx.constraintlayout.core.state.State.Helper!);
+ method public androidx.constraintlayout.core.state.helpers.HorizontalChainReference! horizontalChain();
+ method public androidx.constraintlayout.core.state.helpers.HorizontalChainReference! horizontalChain(java.lang.Object!...!);
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! horizontalGuideline(Object!);
+ method public boolean isBaselineNeeded(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method @Deprecated public boolean isLtr();
+ method public boolean isRtl();
+ method public void map(Object!, Object!);
+ method public void reset();
+ method public boolean sameFixedHeight(int);
+ method public boolean sameFixedWidth(int);
+ method public void setDpToPixel(androidx.constraintlayout.core.state.CorePixelDp!);
+ method public androidx.constraintlayout.core.state.State! setHeight(androidx.constraintlayout.core.state.Dimension!);
+ method @Deprecated public void setLtr(boolean);
+ method public void setRtl(boolean);
+ method public void setTag(String!, String!);
+ method public androidx.constraintlayout.core.state.State! setWidth(androidx.constraintlayout.core.state.Dimension!);
+ method public androidx.constraintlayout.core.state.helpers.VerticalChainReference! verticalChain();
+ method public androidx.constraintlayout.core.state.helpers.VerticalChainReference! verticalChain(java.lang.Object!...!);
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! verticalGuideline(Object!);
+ method public androidx.constraintlayout.core.state.State! width(androidx.constraintlayout.core.state.Dimension!);
+ field public static final Integer PARENT;
+ field protected java.util.HashMap<java.lang.Object!,androidx.constraintlayout.core.state.HelperReference!>! mHelperReferences;
+ field public final androidx.constraintlayout.core.state.ConstraintReference! mParent;
+ field protected java.util.HashMap<java.lang.Object!,androidx.constraintlayout.core.state.Reference!>! mReferences;
+ }
+
+ public enum State.Chain {
+ method public static androidx.constraintlayout.core.state.State.Chain! getChainByString(String!);
+ method public static int getValueByString(String!);
+ enum_constant public static final androidx.constraintlayout.core.state.State.Chain PACKED;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Chain SPREAD;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Chain SPREAD_INSIDE;
+ field public static java.util.Map<java.lang.String!,androidx.constraintlayout.core.state.State.Chain!>! chainMap;
+ field public static java.util.Map<java.lang.String!,java.lang.Integer!>! valueMap;
+ }
+
+ public enum State.Constraint {
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_TOP;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_TOP;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint CENTER_HORIZONTALLY;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint CENTER_VERTICALLY;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint CIRCULAR_CONSTRAINT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint END_TO_END;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint END_TO_START;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint LEFT_TO_LEFT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint LEFT_TO_RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint RIGHT_TO_LEFT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint RIGHT_TO_RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint START_TO_END;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint START_TO_START;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_TOP;
+ }
+
+ public enum State.Direction {
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction END;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction LEFT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction START;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction TOP;
+ }
+
+ public enum State.Helper {
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper ALIGN_HORIZONTALLY;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper ALIGN_VERTICALLY;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper BARRIER;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper COLUMN;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper FLOW;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper GRID;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper HORIZONTAL_CHAIN;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper HORIZONTAL_FLOW;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper LAYER;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper ROW;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper VERTICAL_CHAIN;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper VERTICAL_FLOW;
+ }
+
+ public enum State.Wrap {
+ method public static androidx.constraintlayout.core.state.State.Wrap! getChainByString(String!);
+ method public static int getValueByString(String!);
+ enum_constant public static final androidx.constraintlayout.core.state.State.Wrap ALIGNED;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Wrap CHAIN;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Wrap NONE;
+ field public static java.util.Map<java.lang.String!,java.lang.Integer!>! valueMap;
+ field public static java.util.Map<java.lang.String!,androidx.constraintlayout.core.state.State.Wrap!>! wrapMap;
+ }
+
+ public class Transition implements androidx.constraintlayout.core.motion.utils.TypedValues {
+ ctor public Transition(androidx.constraintlayout.core.state.CorePixelDp);
+ method public void addCustomColor(int, String!, String!, int);
+ method public void addCustomFloat(int, String!, String!, float);
+ method public void addKeyAttribute(String!, androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void addKeyAttribute(String!, androidx.constraintlayout.core.motion.utils.TypedBundle!, androidx.constraintlayout.core.motion.CustomVariable![]!);
+ method public void addKeyCycle(String!, androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void addKeyPosition(String!, androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void addKeyPosition(String!, int, int, float, float);
+ method public void calcStagger();
+ method public void clear();
+ method public boolean contains(String!);
+ method public float dragToProgress(float, int, int, float, float);
+ method public void fillKeyPositions(androidx.constraintlayout.core.state.WidgetFrame!, float[]!, float[]!, float[]!);
+ method public androidx.constraintlayout.core.state.Transition.KeyPosition! findNextPosition(String!, int);
+ method public androidx.constraintlayout.core.state.Transition.KeyPosition! findPreviousPosition(String!, int);
+ method public int getAutoTransition();
+ method public androidx.constraintlayout.core.state.WidgetFrame! getEnd(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public androidx.constraintlayout.core.state.WidgetFrame! getEnd(String!);
+ method public int getId(String!);
+ method public androidx.constraintlayout.core.state.WidgetFrame! getInterpolated(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public androidx.constraintlayout.core.state.WidgetFrame! getInterpolated(String!);
+ method public int getInterpolatedHeight();
+ method public int getInterpolatedWidth();
+ method public androidx.constraintlayout.core.state.Interpolator! getInterpolator();
+ method public static androidx.constraintlayout.core.state.Interpolator! getInterpolator(int, String!);
+ method public int getKeyFrames(String!, float[]!, int[]!, int[]!);
+ method public androidx.constraintlayout.core.motion.Motion! getMotion(String!);
+ method public int getNumberKeyPositions(androidx.constraintlayout.core.state.WidgetFrame!);
+ method public float[]! getPath(String!);
+ method public androidx.constraintlayout.core.state.WidgetFrame! getStart(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public androidx.constraintlayout.core.state.WidgetFrame! getStart(String!);
+ method public float getTouchUpProgress(long);
+ method public androidx.constraintlayout.core.state.Transition.WidgetState! getWidgetState(String!, androidx.constraintlayout.core.widgets.ConstraintWidget!, int);
+ method public boolean hasOnSwipe();
+ method public boolean hasPositionKeyframes();
+ method public void interpolate(int, int, float);
+ method public boolean isEmpty();
+ method public boolean isTouchNotDone(float);
+ method public void setTouchUp(float, long, float, float);
+ method public void setTransitionProperties(androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public boolean setValue(int, boolean);
+ method public boolean setValue(int, float);
+ method public boolean setValue(int, int);
+ method public boolean setValue(int, String!);
+ method public void updateFrom(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, int);
+ field public static final int END = 1; // 0x1
+ field public static final int INTERPOLATED = 2; // 0x2
+ field public static final int START = 0; // 0x0
+ }
+
+ public static class Transition.WidgetState {
+ ctor public Transition.WidgetState();
+ method public androidx.constraintlayout.core.state.WidgetFrame! getFrame(int);
+ method public void interpolate(int, int, float, androidx.constraintlayout.core.state.Transition!);
+ method public void setKeyAttribute(androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void setKeyAttribute(androidx.constraintlayout.core.motion.utils.TypedBundle!, androidx.constraintlayout.core.motion.CustomVariable![]!);
+ method public void setKeyCycle(androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void setKeyPosition(androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void setPathRelative(androidx.constraintlayout.core.state.Transition.WidgetState!);
+ method public void update(androidx.constraintlayout.core.widgets.ConstraintWidget!, int);
+ }
+
+ public class TransitionParser {
+ ctor public TransitionParser();
+ method @Deprecated public static void parse(androidx.constraintlayout.core.parser.CLObject!, androidx.constraintlayout.core.state.Transition!, androidx.constraintlayout.core.state.CorePixelDp!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public static void parseKeyFrames(androidx.constraintlayout.core.parser.CLObject!, androidx.constraintlayout.core.state.Transition!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ }
+
+ public class WidgetFrame {
+ ctor public WidgetFrame();
+ ctor public WidgetFrame(androidx.constraintlayout.core.state.WidgetFrame!);
+ ctor public WidgetFrame(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void addCustomColor(String!, int);
+ method public void addCustomFloat(String!, float);
+ method public float centerX();
+ method public float centerY();
+ method public boolean containsCustom(String);
+ method public androidx.constraintlayout.core.motion.CustomVariable! getCustomAttribute(String!);
+ method public java.util.Set<java.lang.String!>! getCustomAttributeNames();
+ method public int getCustomColor(String!);
+ method public float getCustomFloat(String!);
+ method public String! getId();
+ method public androidx.constraintlayout.core.motion.utils.TypedBundle! getMotionProperties();
+ method public int height();
+ method public static void interpolate(int, int, androidx.constraintlayout.core.state.WidgetFrame!, androidx.constraintlayout.core.state.WidgetFrame!, androidx.constraintlayout.core.state.WidgetFrame!, androidx.constraintlayout.core.state.Transition!, float);
+ method public boolean isDefaultTransform();
+ method public StringBuilder! serialize(StringBuilder!);
+ method public StringBuilder! serialize(StringBuilder!, boolean);
+ method public void setCustomAttribute(String!, int, boolean);
+ method public void setCustomAttribute(String!, int, float);
+ method public void setCustomAttribute(String!, int, int);
+ method public void setCustomAttribute(String!, int, String!);
+ method public void setCustomValue(androidx.constraintlayout.core.motion.CustomAttribute!, float[]!);
+ method public boolean setValue(String!, androidx.constraintlayout.core.parser.CLElement!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.state.WidgetFrame! update();
+ method public androidx.constraintlayout.core.state.WidgetFrame! update(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void updateAttributes(androidx.constraintlayout.core.state.WidgetFrame!);
+ method public int width();
+ field public float alpha;
+ field public int bottom;
+ field public float interpolatedPos;
+ field public int left;
+ field public String! name;
+ field public static float phone_orientation;
+ field public float pivotX;
+ field public float pivotY;
+ field public int right;
+ field public float rotationX;
+ field public float rotationY;
+ field public float rotationZ;
+ field public float scaleX;
+ field public float scaleY;
+ field public int top;
+ field public float translationX;
+ field public float translationY;
+ field public float translationZ;
+ field public int visibility;
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget! widget;
+ }
+
+}
+
+package androidx.constraintlayout.core.state.helpers {
+
+ public class AlignHorizontallyReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public AlignHorizontallyReference(androidx.constraintlayout.core.state.State!);
+ }
+
+ public class AlignVerticallyReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public AlignVerticallyReference(androidx.constraintlayout.core.state.State!);
+ }
+
+ public class BarrierReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public BarrierReference(androidx.constraintlayout.core.state.State!);
+ method public void setBarrierDirection(androidx.constraintlayout.core.state.State.Direction!);
+ }
+
+ public class ChainReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public ChainReference(androidx.constraintlayout.core.state.State, androidx.constraintlayout.core.state.State.Helper);
+ method public void addChainElement(String, float, float, float);
+ method public androidx.constraintlayout.core.state.helpers.ChainReference bias(float);
+ method public float getBias();
+ method protected float getPostMargin(String);
+ method protected float getPreMargin(String);
+ method public androidx.constraintlayout.core.state.State.Chain getStyle();
+ method protected float getWeight(String);
+ method public androidx.constraintlayout.core.state.helpers.ChainReference style(androidx.constraintlayout.core.state.State.Chain);
+ field protected float mBias;
+ field @Deprecated protected java.util.HashMap<java.lang.String!,java.lang.Float!> mMapPostMargin;
+ field @Deprecated protected java.util.HashMap<java.lang.String!,java.lang.Float!> mMapPreMargin;
+ field @Deprecated protected java.util.HashMap<java.lang.String!,java.lang.Float!> mMapWeights;
+ field protected androidx.constraintlayout.core.state.State.Chain mStyle;
+ }
+
+ public interface Facade {
+ method public void apply();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getConstraintWidget();
+ }
+
+ public class FlowReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public FlowReference(androidx.constraintlayout.core.state.State!, androidx.constraintlayout.core.state.State.Helper!);
+ method public void addFlowElement(String!, float, float, float);
+ method public float getFirstHorizontalBias();
+ method public int getFirstHorizontalStyle();
+ method public float getFirstVerticalBias();
+ method public int getFirstVerticalStyle();
+ method public int getHorizontalAlign();
+ method public float getHorizontalBias();
+ method public int getHorizontalGap();
+ method public int getHorizontalStyle();
+ method public float getLastHorizontalBias();
+ method public int getLastHorizontalStyle();
+ method public float getLastVerticalBias();
+ method public int getLastVerticalStyle();
+ method public int getMaxElementsWrap();
+ method public int getOrientation();
+ method public int getPaddingBottom();
+ method public int getPaddingLeft();
+ method public int getPaddingRight();
+ method public int getPaddingTop();
+ method protected float getPostMargin(String!);
+ method protected float getPreMargin(String!);
+ method public int getVerticalAlign();
+ method public float getVerticalBias();
+ method public int getVerticalGap();
+ method public int getVerticalStyle();
+ method protected float getWeight(String!);
+ method public int getWrapMode();
+ method public void setFirstHorizontalBias(float);
+ method public void setFirstHorizontalStyle(int);
+ method public void setFirstVerticalBias(float);
+ method public void setFirstVerticalStyle(int);
+ method public void setHorizontalAlign(int);
+ method public void setHorizontalGap(int);
+ method public void setHorizontalStyle(int);
+ method public void setLastHorizontalBias(float);
+ method public void setLastHorizontalStyle(int);
+ method public void setLastVerticalBias(float);
+ method public void setLastVerticalStyle(int);
+ method public void setMaxElementsWrap(int);
+ method public void setOrientation(int);
+ method public void setPaddingBottom(int);
+ method public void setPaddingLeft(int);
+ method public void setPaddingRight(int);
+ method public void setPaddingTop(int);
+ method public void setVerticalAlign(int);
+ method public void setVerticalGap(int);
+ method public void setVerticalStyle(int);
+ method public void setWrapMode(int);
+ field protected float mFirstHorizontalBias;
+ field protected int mFirstHorizontalStyle;
+ field protected float mFirstVerticalBias;
+ field protected int mFirstVerticalStyle;
+ field protected androidx.constraintlayout.core.widgets.Flow! mFlow;
+ field protected int mHorizontalAlign;
+ field protected int mHorizontalGap;
+ field protected int mHorizontalStyle;
+ field protected float mLastHorizontalBias;
+ field protected int mLastHorizontalStyle;
+ field protected float mLastVerticalBias;
+ field protected int mLastVerticalStyle;
+ field protected java.util.HashMap<java.lang.String!,java.lang.Float!>! mMapPostMargin;
+ field protected java.util.HashMap<java.lang.String!,java.lang.Float!>! mMapPreMargin;
+ field protected java.util.HashMap<java.lang.String!,java.lang.Float!>! mMapWeights;
+ field protected int mMaxElementsWrap;
+ field protected int mOrientation;
+ field protected int mPaddingBottom;
+ field protected int mPaddingLeft;
+ field protected int mPaddingRight;
+ field protected int mPaddingTop;
+ field protected int mVerticalAlign;
+ field protected int mVerticalGap;
+ field protected int mVerticalStyle;
+ field protected int mWrapMode;
+ }
+
+ public class GridReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public GridReference(androidx.constraintlayout.core.state.State, androidx.constraintlayout.core.state.State.Helper);
+ method public String? getColumnWeights();
+ method public int getColumnsSet();
+ method public int getFlags();
+ method public float getHorizontalGaps();
+ method public int getOrientation();
+ method public int getPaddingBottom();
+ method public int getPaddingEnd();
+ method public int getPaddingStart();
+ method public int getPaddingTop();
+ method public String? getRowWeights();
+ method public int getRowsSet();
+ method public String? getSkips();
+ method public String? getSpans();
+ method public float getVerticalGaps();
+ method public void setColumnWeights(String);
+ method public void setColumnsSet(int);
+ method public void setFlags(int);
+ method public void setFlags(String);
+ method public void setHorizontalGaps(float);
+ method public void setOrientation(int);
+ method public void setPaddingBottom(int);
+ method public void setPaddingEnd(int);
+ method public void setPaddingStart(int);
+ method public void setPaddingTop(int);
+ method public void setRowWeights(String);
+ method public void setRowsSet(int);
+ method public void setSkips(String);
+ method public void setSpans(String);
+ method public void setVerticalGaps(float);
+ }
+
+ public class GuidelineReference implements androidx.constraintlayout.core.state.helpers.Facade androidx.constraintlayout.core.state.Reference {
+ ctor public GuidelineReference(androidx.constraintlayout.core.state.State!);
+ method public void apply();
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! end(Object!);
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getConstraintWidget();
+ method public androidx.constraintlayout.core.state.helpers.Facade! getFacade();
+ method public Object! getKey();
+ method public int getOrientation();
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! percent(float);
+ method public void setConstraintWidget(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void setKey(Object!);
+ method public void setOrientation(int);
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! start(Object!);
+ }
+
+ public class HorizontalChainReference extends androidx.constraintlayout.core.state.helpers.ChainReference {
+ ctor public HorizontalChainReference(androidx.constraintlayout.core.state.State!);
+ }
+
+ public class VerticalChainReference extends androidx.constraintlayout.core.state.helpers.ChainReference {
+ ctor public VerticalChainReference(androidx.constraintlayout.core.state.State!);
+ }
+
+}
+
+package androidx.constraintlayout.core.utils {
+
+ public class GridCore extends androidx.constraintlayout.core.widgets.VirtualLayout {
+ ctor public GridCore();
+ ctor public GridCore(int, int);
+ method public String? getColumnWeights();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidgetContainer? getContainer();
+ method public int getFlags();
+ method public float getHorizontalGaps();
+ method public int getOrientation();
+ method public String? getRowWeights();
+ method public float getVerticalGaps();
+ method public void setColumnWeights(String);
+ method public void setColumns(int);
+ method public void setContainer(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer);
+ method public void setFlags(int);
+ method public void setHorizontalGaps(float);
+ method public void setOrientation(int);
+ method public void setRowWeights(String);
+ method public void setRows(int);
+ method public void setSkips(String);
+ method public void setSpans(CharSequence);
+ method public void setVerticalGaps(float);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int SPANS_RESPECT_WIDGET_ORDER = 2; // 0x2
+ field public static final int SUB_GRID_BY_COL_ROW = 1; // 0x1
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public class GridEngine {
+ ctor public GridEngine();
+ ctor public GridEngine(int, int);
+ ctor public GridEngine(int, int, int);
+ method public int bottomOfWidget(int);
+ method public int leftOfWidget(int);
+ method public int rightOfWidget(int);
+ method public void setColumns(int);
+ method public void setNumWidgets(int);
+ method public void setOrientation(int);
+ method public void setRows(int);
+ method public void setSkips(String!);
+ method public void setSpans(CharSequence!);
+ method public void setup();
+ method public int topOfWidget(int);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+}
+
+package androidx.constraintlayout.core.widgets {
+
+ public class Barrier extends androidx.constraintlayout.core.widgets.HelperWidget {
+ ctor public Barrier();
+ ctor public Barrier(String!);
+ method public boolean allSolved();
+ method @Deprecated public boolean allowsGoneWidget();
+ method public boolean getAllowsGoneWidget();
+ method public int getBarrierType();
+ method public int getMargin();
+ method public int getOrientation();
+ method protected void markWidgets();
+ method public void setAllowsGoneWidget(boolean);
+ method public void setBarrierType(int);
+ method public void setMargin(int);
+ field public static final int BOTTOM = 3; // 0x3
+ field public static final int LEFT = 0; // 0x0
+ field public static final int RIGHT = 1; // 0x1
+ field public static final int TOP = 2; // 0x2
+ }
+
+ public class Chain {
+ ctor public Chain();
+ method public static void applyChainConstraints(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.LinearSystem!, java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintWidget!>!, int);
+ field public static final boolean USE_CHAIN_OPTIMIZATION = false;
+ }
+
+ public class ChainHead {
+ ctor public ChainHead(androidx.constraintlayout.core.widgets.ConstraintWidget!, int, boolean);
+ method public void define();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getFirst();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getFirstMatchConstraintWidget();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getFirstVisibleWidget();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getHead();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getLast();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getLastMatchConstraintWidget();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getLastVisibleWidget();
+ method public float getTotalWeight();
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mFirst;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mFirstMatchConstraintWidget;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mFirstVisibleWidget;
+ field protected boolean mHasComplexMatchWeights;
+ field protected boolean mHasDefinedWeights;
+ field protected boolean mHasRatio;
+ field protected boolean mHasUndefinedWeights;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mHead;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mLast;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mLastMatchConstraintWidget;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mLastVisibleWidget;
+ field protected float mTotalWeight;
+ field protected java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintWidget!>! mWeightedMatchConstraintsWidgets;
+ field protected int mWidgetsCount;
+ field protected int mWidgetsMatchCount;
+ }
+
+ public class ConstraintAnchor {
+ ctor public ConstraintAnchor(androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!);
+ method public boolean connect(androidx.constraintlayout.core.widgets.ConstraintAnchor!, int);
+ method public boolean connect(androidx.constraintlayout.core.widgets.ConstraintAnchor!, int, int, boolean);
+ method public void copyFrom(androidx.constraintlayout.core.widgets.ConstraintAnchor!, java.util.HashMap<androidx.constraintlayout.core.widgets.ConstraintWidget!,androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public void findDependents(int, java.util.ArrayList<androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!>!, androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!);
+ method public java.util.HashSet<androidx.constraintlayout.core.widgets.ConstraintAnchor!>! getDependents();
+ method public int getFinalValue();
+ method public int getMargin();
+ method public final androidx.constraintlayout.core.widgets.ConstraintAnchor! getOpposite();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getOwner();
+ method public androidx.constraintlayout.core.SolverVariable! getSolverVariable();
+ method public androidx.constraintlayout.core.widgets.ConstraintAnchor! getTarget();
+ method public androidx.constraintlayout.core.widgets.ConstraintAnchor.Type! getType();
+ method public boolean hasCenteredDependents();
+ method public boolean hasDependents();
+ method public boolean hasFinalValue();
+ method public boolean isConnected();
+ method public boolean isConnectionAllowed(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public boolean isConnectionAllowed(androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public boolean isSideAnchor();
+ method public boolean isSimilarDimensionConnection(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public boolean isValidConnection(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public boolean isVerticalAnchor();
+ method public void reset();
+ method public void resetFinalResolution();
+ method public void resetSolverVariable(androidx.constraintlayout.core.Cache!);
+ method public void setFinalValue(int);
+ method public void setGoneMargin(int);
+ method public void setMargin(int);
+ field public int mMargin;
+ field public final androidx.constraintlayout.core.widgets.ConstraintWidget! mOwner;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mTarget;
+ field public final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type! mType;
+ }
+
+ public enum ConstraintAnchor.Type {
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type CENTER;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type CENTER_X;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type CENTER_Y;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type LEFT;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type NONE;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type TOP;
+ }
+
+ public class ConstraintWidget {
+ ctor public ConstraintWidget();
+ ctor public ConstraintWidget(int, int);
+ ctor public ConstraintWidget(int, int, int, int);
+ ctor public ConstraintWidget(String!);
+ ctor public ConstraintWidget(String!, int, int);
+ ctor public ConstraintWidget(String!, int, int, int, int);
+ method public void addChildrenToSolverByDependency(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.LinearSystem!, java.util.HashSet<androidx.constraintlayout.core.widgets.ConstraintWidget!>!, int, boolean);
+ method public void addToSolver(androidx.constraintlayout.core.LinearSystem!, boolean);
+ method public boolean allowedInBarrier();
+ method public void connect(androidx.constraintlayout.core.widgets.ConstraintAnchor!, androidx.constraintlayout.core.widgets.ConstraintAnchor!, int);
+ method public void connect(androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!);
+ method public void connect(androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, int);
+ method public void connectCircularConstraint(androidx.constraintlayout.core.widgets.ConstraintWidget!, float, int);
+ method public void copy(androidx.constraintlayout.core.widgets.ConstraintWidget!, java.util.HashMap<androidx.constraintlayout.core.widgets.ConstraintWidget!,androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public void createObjectVariables(androidx.constraintlayout.core.LinearSystem!);
+ method public void ensureMeasureRequested();
+ method public void ensureWidgetRuns();
+ method public androidx.constraintlayout.core.widgets.ConstraintAnchor! getAnchor(androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!);
+ method public java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintAnchor!>! getAnchors();
+ method public int getBaselineDistance();
+ method public float getBiasPercent(int);
+ method public int getBottom();
+ method public Object! getCompanionWidget();
+ method public int getContainerItemSkip();
+ method public String! getDebugName();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! getDimensionBehaviour(int);
+ method public float getDimensionRatio();
+ method public int getDimensionRatioSide();
+ method public boolean getHasBaseline();
+ method public int getHeight();
+ method public float getHorizontalBiasPercent();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getHorizontalChainControlWidget();
+ method public int getHorizontalChainStyle();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! getHorizontalDimensionBehaviour();
+ method public int getHorizontalMargin();
+ method public int getLastHorizontalMeasureSpec();
+ method public int getLastVerticalMeasureSpec();
+ method public int getLeft();
+ method public int getLength(int);
+ method public int getMaxHeight();
+ method public int getMaxWidth();
+ method public int getMinHeight();
+ method public int getMinWidth();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getNextChainMember(int);
+ method public int getOptimizerWrapHeight();
+ method public int getOptimizerWrapWidth();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getParent();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getPreviousChainMember(int);
+ method public int getRight();
+ method protected int getRootX();
+ method protected int getRootY();
+ method public androidx.constraintlayout.core.widgets.analyzer.WidgetRun! getRun(int);
+ method public void getSceneString(StringBuilder!);
+ method public int getTop();
+ method public String! getType();
+ method public float getVerticalBiasPercent();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getVerticalChainControlWidget();
+ method public int getVerticalChainStyle();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! getVerticalDimensionBehaviour();
+ method public int getVerticalMargin();
+ method public int getVisibility();
+ method public int getWidth();
+ method public int getWrapBehaviorInParent();
+ method public int getX();
+ method public int getY();
+ method public boolean hasBaseline();
+ method public boolean hasDanglingDimension(int);
+ method public boolean hasDependencies();
+ method public boolean hasDimensionOverride();
+ method public boolean hasResolvedTargets(int, int);
+ method public void immediateConnect(androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, int, int);
+ method public boolean isAnimated();
+ method public boolean isHeightWrapContent();
+ method public boolean isHorizontalSolvingPassDone();
+ method public boolean isInBarrier(int);
+ method public boolean isInHorizontalChain();
+ method public boolean isInPlaceholder();
+ method public boolean isInVerticalChain();
+ method public boolean isInVirtualLayout();
+ method public boolean isMeasureRequested();
+ method public boolean isResolvedHorizontally();
+ method public boolean isResolvedVertically();
+ method public boolean isRoot();
+ method public boolean isSpreadHeight();
+ method public boolean isSpreadWidth();
+ method public boolean isVerticalSolvingPassDone();
+ method public boolean isWidthWrapContent();
+ method public void markHorizontalSolvingPassDone();
+ method public void markVerticalSolvingPassDone();
+ method public boolean oppositeDimensionDependsOn(int);
+ method public boolean oppositeDimensionsTied();
+ method public void reset();
+ method public void resetAllConstraints();
+ method public void resetAnchor(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public void resetAnchors();
+ method public void resetFinalResolution();
+ method public void resetSolverVariables(androidx.constraintlayout.core.Cache!);
+ method public void resetSolvingPassFlag();
+ method public StringBuilder! serialize(StringBuilder!);
+ method public void setAnimated(boolean);
+ method public void setBaselineDistance(int);
+ method public void setCompanionWidget(Object!);
+ method public void setContainerItemSkip(int);
+ method public void setDebugName(String!);
+ method public void setDebugSolverName(androidx.constraintlayout.core.LinearSystem!, String!);
+ method public void setDimension(int, int);
+ method public void setDimensionRatio(float, int);
+ method public void setDimensionRatio(String!);
+ method public void setFinalBaseline(int);
+ method public void setFinalFrame(int, int, int, int, int, int);
+ method public void setFinalHorizontal(int, int);
+ method public void setFinalLeft(int);
+ method public void setFinalTop(int);
+ method public void setFinalVertical(int, int);
+ method public void setFrame(int, int, int);
+ method public void setFrame(int, int, int, int);
+ method public void setGoneMargin(androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, int);
+ method public void setHasBaseline(boolean);
+ method public void setHeight(int);
+ method public void setHeightWrapContent(boolean);
+ method public void setHorizontalBiasPercent(float);
+ method public void setHorizontalChainStyle(int);
+ method public void setHorizontalDimension(int, int);
+ method public void setHorizontalDimensionBehaviour(androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!);
+ method public void setHorizontalMatchStyle(int, int, int, float);
+ method public void setHorizontalWeight(float);
+ method protected void setInBarrier(int, boolean);
+ method public void setInPlaceholder(boolean);
+ method public void setInVirtualLayout(boolean);
+ method public void setLastMeasureSpec(int, int);
+ method public void setLength(int, int);
+ method public void setMaxHeight(int);
+ method public void setMaxWidth(int);
+ method public void setMeasureRequested(boolean);
+ method public void setMinHeight(int);
+ method public void setMinWidth(int);
+ method public void setOffset(int, int);
+ method public void setOrigin(int, int);
+ method public void setParent(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void setType(String!);
+ method public void setVerticalBiasPercent(float);
+ method public void setVerticalChainStyle(int);
+ method public void setVerticalDimension(int, int);
+ method public void setVerticalDimensionBehaviour(androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!);
+ method public void setVerticalMatchStyle(int, int, int, float);
+ method public void setVerticalWeight(float);
+ method public void setVisibility(int);
+ method public void setWidth(int);
+ method public void setWidthWrapContent(boolean);
+ method public void setWrapBehaviorInParent(int);
+ method public void setX(int);
+ method public void setY(int);
+ method public void setupDimensionRatio(boolean, boolean, boolean, boolean);
+ method public void updateFromRuns(boolean, boolean);
+ method public void updateFromSolver(androidx.constraintlayout.core.LinearSystem!, boolean);
+ field public static final int ANCHOR_BASELINE = 4; // 0x4
+ field public static final int ANCHOR_BOTTOM = 3; // 0x3
+ field public static final int ANCHOR_LEFT = 0; // 0x0
+ field public static final int ANCHOR_RIGHT = 1; // 0x1
+ field public static final int ANCHOR_TOP = 2; // 0x2
+ field public static final int BOTH = 2; // 0x2
+ field public static final int CHAIN_PACKED = 2; // 0x2
+ field public static final int CHAIN_SPREAD = 0; // 0x0
+ field public static final int CHAIN_SPREAD_INSIDE = 1; // 0x1
+ field public static float DEFAULT_BIAS;
+ field protected static final int DIRECT = 2; // 0x2
+ field public static final int GONE = 8; // 0x8
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int INVISIBLE = 4; // 0x4
+ field public static final int MATCH_CONSTRAINT_PERCENT = 2; // 0x2
+ field public static final int MATCH_CONSTRAINT_RATIO = 3; // 0x3
+ field public static final int MATCH_CONSTRAINT_RATIO_RESOLVED = 4; // 0x4
+ field public static final int MATCH_CONSTRAINT_SPREAD = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_WRAP = 1; // 0x1
+ field protected static final int SOLVER = 1; // 0x1
+ field public static final int UNKNOWN = -1; // 0xffffffff
+ field public static final int VERTICAL = 1; // 0x1
+ field public static final int VISIBLE = 0; // 0x0
+ field public static final int WRAP_BEHAVIOR_HORIZONTAL_ONLY = 1; // 0x1
+ field public static final int WRAP_BEHAVIOR_INCLUDED = 0; // 0x0
+ field public static final int WRAP_BEHAVIOR_SKIPPED = 3; // 0x3
+ field public static final int WRAP_BEHAVIOR_VERTICAL_ONLY = 2; // 0x2
+ field public androidx.constraintlayout.core.state.WidgetFrame! frame;
+ field public androidx.constraintlayout.core.widgets.analyzer.ChainRun! horizontalChainRun;
+ field public int horizontalGroup;
+ field public boolean[]! isTerminalWidget;
+ field protected java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintAnchor!>! mAnchors;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mBaseline;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mBottom;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mCenter;
+ field public float mCircleConstraintAngle;
+ field public float mDimensionRatio;
+ field protected int mDimensionRatioSide;
+ field public int mHorizontalResolution;
+ field public androidx.constraintlayout.core.widgets.analyzer.HorizontalWidgetRun! mHorizontalRun;
+ field public boolean mIsHeightWrapContent;
+ field public boolean mIsWidthWrapContent;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mLeft;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor![]! mListAnchors;
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour![]! mListDimensionBehaviors;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget![]! mListNextMatchConstraintsWidget;
+ field public int mMatchConstraintDefaultHeight;
+ field public int mMatchConstraintDefaultWidth;
+ field public int mMatchConstraintMaxHeight;
+ field public int mMatchConstraintMaxWidth;
+ field public int mMatchConstraintMinHeight;
+ field public int mMatchConstraintMinWidth;
+ field public float mMatchConstraintPercentHeight;
+ field public float mMatchConstraintPercentWidth;
+ field protected int mMinHeight;
+ field protected int mMinWidth;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget![]! mNextChainWidget;
+ field protected int mOffsetX;
+ field protected int mOffsetY;
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget! mParent;
+ field public int[]! mResolvedMatchConstraintDefault;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mRight;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mTop;
+ field public int mVerticalResolution;
+ field public androidx.constraintlayout.core.widgets.analyzer.VerticalWidgetRun! mVerticalRun;
+ field public float[]! mWeight;
+ field protected int mX;
+ field protected int mY;
+ field public boolean measured;
+ field public androidx.constraintlayout.core.widgets.analyzer.WidgetRun![]! run;
+ field public String! stringId;
+ field public androidx.constraintlayout.core.widgets.analyzer.ChainRun! verticalChainRun;
+ field public int verticalGroup;
+ }
+
+ public enum ConstraintWidget.DimensionBehaviour {
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour FIXED;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour MATCH_CONSTRAINT;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour MATCH_PARENT;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour WRAP_CONTENT;
+ }
+
+ public class ConstraintWidgetContainer extends androidx.constraintlayout.core.widgets.WidgetContainer {
+ ctor public ConstraintWidgetContainer();
+ ctor public ConstraintWidgetContainer(int, int);
+ ctor public ConstraintWidgetContainer(int, int, int, int);
+ ctor public ConstraintWidgetContainer(String!, int, int);
+ method public boolean addChildrenToSolver(androidx.constraintlayout.core.LinearSystem!);
+ method public void addHorizontalWrapMaxVariable(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public void addHorizontalWrapMinVariable(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public void defineTerminalWidgets();
+ method public boolean directMeasure(boolean);
+ method public boolean directMeasureSetup(boolean);
+ method public boolean directMeasureWithOrientation(boolean, int);
+ method public void fillMetrics(androidx.constraintlayout.core.Metrics!);
+ method public java.util.ArrayList<androidx.constraintlayout.core.widgets.Guideline!>! getHorizontalGuidelines();
+ method public androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer! getMeasurer();
+ method public int getOptimizationLevel();
+ method public androidx.constraintlayout.core.LinearSystem! getSystem();
+ method public java.util.ArrayList<androidx.constraintlayout.core.widgets.Guideline!>! getVerticalGuidelines();
+ method public boolean handlesInternalConstraints();
+ method public void invalidateGraph();
+ method public void invalidateMeasures();
+ method public boolean isHeightMeasuredTooSmall();
+ method public boolean isRtl();
+ method public boolean isWidthMeasuredTooSmall();
+ method public static boolean measure(int, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer!, androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measure!, int);
+ method public long measure(int, int, int, int, int, int, int, int, int);
+ method public boolean optimizeFor(int);
+ method public void setMeasurer(androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer!);
+ method public void setOptimizationLevel(int);
+ method public void setPadding(int, int, int, int);
+ method public void setPass(int);
+ method public void setRtl(boolean);
+ method public boolean updateChildrenFromSolver(androidx.constraintlayout.core.LinearSystem!, boolean[]!);
+ method public void updateHierarchy();
+ field public androidx.constraintlayout.core.widgets.analyzer.DependencyGraph! mDependencyGraph;
+ field public boolean mGroupsWrapOptimized;
+ field public int mHorizontalChainsSize;
+ field public boolean mHorizontalWrapOptimized;
+ field public androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measure! mMeasure;
+ field protected androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer! mMeasurer;
+ field public androidx.constraintlayout.core.Metrics! mMetrics;
+ field public boolean mSkipSolver;
+ field protected androidx.constraintlayout.core.LinearSystem! mSystem;
+ field public int mVerticalChainsSize;
+ field public boolean mVerticalWrapOptimized;
+ field public int mWrapFixedHeight;
+ field public int mWrapFixedWidth;
+ }
+
+ public class Flow extends androidx.constraintlayout.core.widgets.VirtualLayout {
+ ctor public Flow();
+ method public float getMaxElementsWrap();
+ method public void setFirstHorizontalBias(float);
+ method public void setFirstHorizontalStyle(int);
+ method public void setFirstVerticalBias(float);
+ method public void setFirstVerticalStyle(int);
+ method public void setHorizontalAlign(int);
+ method public void setHorizontalBias(float);
+ method public void setHorizontalGap(int);
+ method public void setHorizontalStyle(int);
+ method public void setLastHorizontalBias(float);
+ method public void setLastHorizontalStyle(int);
+ method public void setLastVerticalBias(float);
+ method public void setLastVerticalStyle(int);
+ method public void setMaxElementsWrap(int);
+ method public void setOrientation(int);
+ method public void setVerticalAlign(int);
+ method public void setVerticalBias(float);
+ method public void setVerticalGap(int);
+ method public void setVerticalStyle(int);
+ method public void setWrapMode(int);
+ field public static final int HORIZONTAL_ALIGN_CENTER = 2; // 0x2
+ field public static final int HORIZONTAL_ALIGN_END = 1; // 0x1
+ field public static final int HORIZONTAL_ALIGN_START = 0; // 0x0
+ field public static final int VERTICAL_ALIGN_BASELINE = 3; // 0x3
+ field public static final int VERTICAL_ALIGN_BOTTOM = 1; // 0x1
+ field public static final int VERTICAL_ALIGN_CENTER = 2; // 0x2
+ field public static final int VERTICAL_ALIGN_TOP = 0; // 0x0
+ field public static final int WRAP_ALIGNED = 2; // 0x2
+ field public static final int WRAP_CHAIN = 1; // 0x1
+ field public static final int WRAP_CHAIN_NEW = 3; // 0x3
+ field public static final int WRAP_NONE = 0; // 0x0
+ }
+
+ public class Guideline extends androidx.constraintlayout.core.widgets.ConstraintWidget {
+ ctor public Guideline();
+ method public void cyclePosition();
+ method public androidx.constraintlayout.core.widgets.ConstraintAnchor! getAnchor();
+ method public int getMinimumPosition();
+ method public int getOrientation();
+ method public int getRelativeBegin();
+ method public int getRelativeBehaviour();
+ method public int getRelativeEnd();
+ method public float getRelativePercent();
+ method public boolean isPercent();
+ method public void setFinalValue(int);
+ method public void setGuideBegin(int);
+ method public void setGuideEnd(int);
+ method public void setGuidePercent(float);
+ method public void setGuidePercent(int);
+ method public void setMinimumPosition(int);
+ method public void setOrientation(int);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int RELATIVE_BEGIN = 1; // 0x1
+ field public static final int RELATIVE_END = 2; // 0x2
+ field public static final int RELATIVE_PERCENT = 0; // 0x0
+ field public static final int RELATIVE_UNKNOWN = -1; // 0xffffffff
+ field public static final int VERTICAL = 1; // 0x1
+ field protected boolean mGuidelineUseRtl;
+ field protected int mRelativeBegin;
+ field protected int mRelativeEnd;
+ field protected float mRelativePercent;
+ }
+
+ public interface Helper {
+ method public void add(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void removeAllIds();
+ method public void updateConstraints(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ }
+
+ public class HelperWidget extends androidx.constraintlayout.core.widgets.ConstraintWidget implements androidx.constraintlayout.core.widgets.Helper {
+ ctor public HelperWidget();
+ method public void add(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void addDependents(java.util.ArrayList<androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!>!, int, androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!);
+ method public int findGroupInDependents(int);
+ method public void removeAllIds();
+ method public void updateConstraints(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget![]! mWidgets;
+ field public int mWidgetsCount;
+ }
+
+ public class Optimizer {
+ ctor public Optimizer();
+ method public static final boolean enabled(int, int);
+ field public static final int OPTIMIZATION_BARRIER = 2; // 0x2
+ field public static final int OPTIMIZATION_CACHE_MEASURES = 256; // 0x100
+ field public static final int OPTIMIZATION_CHAIN = 4; // 0x4
+ field public static final int OPTIMIZATION_DEPENDENCY_ORDERING = 512; // 0x200
+ field public static final int OPTIMIZATION_DIMENSIONS = 8; // 0x8
+ field public static final int OPTIMIZATION_DIRECT = 1; // 0x1
+ field public static final int OPTIMIZATION_GRAPH = 64; // 0x40
+ field public static final int OPTIMIZATION_GRAPH_WRAP = 128; // 0x80
+ field public static final int OPTIMIZATION_GROUPING = 1024; // 0x400
+ field public static final int OPTIMIZATION_GROUPS = 32; // 0x20
+ field public static final int OPTIMIZATION_NONE = 0; // 0x0
+ field public static final int OPTIMIZATION_RATIO = 16; // 0x10
+ field public static final int OPTIMIZATION_STANDARD = 257; // 0x101
+ }
+
+ public class Placeholder extends androidx.constraintlayout.core.widgets.VirtualLayout {
+ ctor public Placeholder();
+ }
+
+ public class Rectangle {
+ ctor public Rectangle();
+ method public boolean contains(int, int);
+ method public int getCenterX();
+ method public int getCenterY();
+ method public void setBounds(int, int, int, int);
+ field public int height;
+ field public int width;
+ field public int x;
+ field public int y;
+ }
+
+ public class VirtualLayout extends androidx.constraintlayout.core.widgets.HelperWidget {
+ ctor public VirtualLayout();
+ method public void applyRtl(boolean);
+ method public void captureWidgets();
+ method public boolean contains(java.util.HashSet<androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public int getMeasuredHeight();
+ method public int getMeasuredWidth();
+ method public int getPaddingBottom();
+ method public int getPaddingLeft();
+ method public int getPaddingRight();
+ method public int getPaddingTop();
+ method protected void measure(androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, int, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, int);
+ method public void measure(int, int, int, int);
+ method protected boolean measureChildren();
+ method public boolean needSolverPass();
+ method protected void needsCallbackFromSolver(boolean);
+ method public void setMeasure(int, int);
+ method public void setPadding(int);
+ method public void setPaddingBottom(int);
+ method public void setPaddingEnd(int);
+ method public void setPaddingLeft(int);
+ method public void setPaddingRight(int);
+ method public void setPaddingStart(int);
+ method public void setPaddingTop(int);
+ field protected androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measure! mMeasure;
+ }
+
+ public class WidgetContainer extends androidx.constraintlayout.core.widgets.ConstraintWidget {
+ ctor public WidgetContainer();
+ ctor public WidgetContainer(int, int);
+ ctor public WidgetContainer(int, int, int, int);
+ method public void add(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void add(androidx.constraintlayout.core.widgets.ConstraintWidget!...!);
+ method public java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintWidget!>! getChildren();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidgetContainer! getRootConstraintContainer();
+ method public void layout();
+ method public void remove(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void removeAllChildren();
+ field public java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintWidget!>! mChildren;
+ }
+
+}
+
+package androidx.constraintlayout.core.widgets.analyzer {
+
+ public class BasicMeasure {
+ ctor public BasicMeasure(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ method public long solverMeasure(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, int, int, int, int, int, int, int, int, int);
+ method public void updateHierarchy(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ field public static final int AT_MOST = -2147483648; // 0x80000000
+ field public static final int EXACTLY = 1073741824; // 0x40000000
+ field public static final int FIXED = -3; // 0xfffffffd
+ field public static final int MATCH_PARENT = -1; // 0xffffffff
+ field public static final int UNSPECIFIED = 0; // 0x0
+ field public static final int WRAP_CONTENT = -2; // 0xfffffffe
+ }
+
+ public static class BasicMeasure.Measure {
+ ctor public BasicMeasure.Measure();
+ field public static int SELF_DIMENSIONS;
+ field public static int TRY_GIVEN_DIMENSIONS;
+ field public static int USE_GIVEN_DIMENSIONS;
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! horizontalBehavior;
+ field public int horizontalDimension;
+ field public int measureStrategy;
+ field public int measuredBaseline;
+ field public boolean measuredHasBaseline;
+ field public int measuredHeight;
+ field public boolean measuredNeedsSolverPass;
+ field public int measuredWidth;
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! verticalBehavior;
+ field public int verticalDimension;
+ }
+
+ public static interface BasicMeasure.Measurer {
+ method public void didMeasures();
+ method public void measure(androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measure!);
+ }
+
+ public class ChainRun extends androidx.constraintlayout.core.widgets.analyzer.WidgetRun {
+ ctor public ChainRun(androidx.constraintlayout.core.widgets.ConstraintWidget!, int);
+ method public void applyToWidget();
+ }
+
+ public interface Dependency {
+ method public void update(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ }
+
+ public class DependencyGraph {
+ ctor public DependencyGraph(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ method public void buildGraph();
+ method public void buildGraph(java.util.ArrayList<androidx.constraintlayout.core.widgets.analyzer.WidgetRun!>!);
+ method public void defineTerminalWidgets(androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!);
+ method public boolean directMeasure(boolean);
+ method public boolean directMeasureSetup(boolean);
+ method public boolean directMeasureWithOrientation(boolean, int);
+ method public void invalidateGraph();
+ method public void invalidateMeasures();
+ method public void measureWidgets();
+ method public void setMeasurer(androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer!);
+ }
+
+ public class DependencyNode implements androidx.constraintlayout.core.widgets.analyzer.Dependency {
+ ctor public DependencyNode(androidx.constraintlayout.core.widgets.analyzer.WidgetRun!);
+ method public void addDependency(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ method public void clear();
+ method public String! name();
+ method public void resolve(int);
+ method public void update(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ field public boolean delegateToWidgetRun;
+ field public boolean readyToSolve;
+ field public boolean resolved;
+ field public androidx.constraintlayout.core.widgets.analyzer.Dependency! updateDelegate;
+ field public int value;
+ }
+
+ public class Direct {
+ ctor public Direct();
+ method public static String! ls(int);
+ method public static boolean solveChain(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.LinearSystem!, int, int, androidx.constraintlayout.core.widgets.ChainHead!, boolean, boolean, boolean);
+ method public static void solvingPass(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer!);
+ }
+
+ public class Grouping {
+ ctor public Grouping();
+ method public static androidx.constraintlayout.core.widgets.analyzer.WidgetGroup! findDependents(androidx.constraintlayout.core.widgets.ConstraintWidget!, int, java.util.ArrayList<androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!>!, androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!);
+ method public static boolean simpleSolvingPass(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer!);
+ method public static boolean validInGroup(androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!);
+ }
+
+ public class HorizontalWidgetRun extends androidx.constraintlayout.core.widgets.analyzer.WidgetRun {
+ ctor public HorizontalWidgetRun(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void applyToWidget();
+ }
+
+ public class VerticalWidgetRun extends androidx.constraintlayout.core.widgets.analyzer.WidgetRun {
+ ctor public VerticalWidgetRun(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void applyToWidget();
+ field public androidx.constraintlayout.core.widgets.analyzer.DependencyNode! baseline;
+ }
+
+ public class WidgetGroup {
+ ctor public WidgetGroup(int);
+ method public boolean add(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void apply();
+ method public void cleanup(java.util.ArrayList<androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!>!);
+ method public void clear();
+ method public int getId();
+ method public int getOrientation();
+ method public boolean intersectWith(androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!);
+ method public boolean isAuthoritative();
+ method public int measureWrap(androidx.constraintlayout.core.LinearSystem!, int);
+ method public void moveTo(int, androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!);
+ method public void setAuthoritative(boolean);
+ method public void setOrientation(int);
+ method public int size();
+ }
+
+ public abstract class WidgetRun implements androidx.constraintlayout.core.widgets.analyzer.Dependency {
+ ctor public WidgetRun(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method protected final void addTarget(androidx.constraintlayout.core.widgets.analyzer.DependencyNode!, androidx.constraintlayout.core.widgets.analyzer.DependencyNode!, int);
+ method protected final void addTarget(androidx.constraintlayout.core.widgets.analyzer.DependencyNode!, androidx.constraintlayout.core.widgets.analyzer.DependencyNode!, int, androidx.constraintlayout.core.widgets.analyzer.DimensionDependency!);
+ method protected final int getLimitedDimension(int, int);
+ method protected final androidx.constraintlayout.core.widgets.analyzer.DependencyNode! getTarget(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method protected final androidx.constraintlayout.core.widgets.analyzer.DependencyNode! getTarget(androidx.constraintlayout.core.widgets.ConstraintAnchor!, int);
+ method public long getWrapDimension();
+ method public boolean isCenterConnection();
+ method public boolean isDimensionResolved();
+ method public boolean isResolved();
+ method public void update(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ method protected void updateRunCenter(androidx.constraintlayout.core.widgets.analyzer.Dependency!, androidx.constraintlayout.core.widgets.ConstraintAnchor!, androidx.constraintlayout.core.widgets.ConstraintAnchor!, int);
+ method protected void updateRunEnd(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ method protected void updateRunStart(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ method public long wrapSize(int);
+ field public androidx.constraintlayout.core.widgets.analyzer.DependencyNode! end;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! mDimensionBehavior;
+ field protected androidx.constraintlayout.core.widgets.analyzer.WidgetRun.RunType! mRunType;
+ field public int matchConstraintsType;
+ field public int orientation;
+ field public androidx.constraintlayout.core.widgets.analyzer.DependencyNode! start;
+ }
+
+}
+
diff --git a/constraintlayout/constraintlayout-core/api/restricted_1.1.0-beta01.txt b/constraintlayout/constraintlayout-core/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..daaf9e5
--- /dev/null
+++ b/constraintlayout/constraintlayout-core/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,3397 @@
+// Signature format: 4.0
+package androidx.constraintlayout.core {
+
+ public class ArrayLinkedVariables implements androidx.constraintlayout.core.ArrayRow.ArrayRowVariables {
+ method public void add(androidx.constraintlayout.core.SolverVariable!, float, boolean);
+ method public final void clear();
+ method public boolean contains(androidx.constraintlayout.core.SolverVariable!);
+ method public void display();
+ method public void divideByAmount(float);
+ method public final float get(androidx.constraintlayout.core.SolverVariable!);
+ method public int getCurrentSize();
+ method public int getHead();
+ method public final int getId(int);
+ method public final int getNextIndice(int);
+ method public final float getValue(int);
+ method public androidx.constraintlayout.core.SolverVariable! getVariable(int);
+ method public float getVariableValue(int);
+ method public int indexOf(androidx.constraintlayout.core.SolverVariable!);
+ method public void invert();
+ method public final void put(androidx.constraintlayout.core.SolverVariable!, float);
+ method public final float remove(androidx.constraintlayout.core.SolverVariable!, boolean);
+ method public int sizeInBytes();
+ method public float use(androidx.constraintlayout.core.ArrayRow!, boolean);
+ field protected final androidx.constraintlayout.core.Cache! mCache;
+ }
+
+ public class ArrayRow {
+ ctor public ArrayRow();
+ ctor public ArrayRow(androidx.constraintlayout.core.Cache!);
+ method public androidx.constraintlayout.core.ArrayRow! addError(androidx.constraintlayout.core.LinearSystem!, int);
+ method public void addError(androidx.constraintlayout.core.SolverVariable!);
+ method public void clear();
+ method public androidx.constraintlayout.core.ArrayRow! createRowDimensionRatio(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, float);
+ method public androidx.constraintlayout.core.ArrayRow! createRowEqualDimension(float, float, float, androidx.constraintlayout.core.SolverVariable!, int, androidx.constraintlayout.core.SolverVariable!, int, androidx.constraintlayout.core.SolverVariable!, int, androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.ArrayRow! createRowEqualMatchDimensions(float, float, float, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!);
+ method public androidx.constraintlayout.core.ArrayRow! createRowEquals(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.ArrayRow! createRowEquals(androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.ArrayRow! createRowGreaterThan(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.ArrayRow! createRowGreaterThan(androidx.constraintlayout.core.SolverVariable!, int, androidx.constraintlayout.core.SolverVariable!);
+ method public androidx.constraintlayout.core.ArrayRow! createRowLowerThan(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.ArrayRow! createRowWithAngle(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, float);
+ method public androidx.constraintlayout.core.SolverVariable! getKey();
+ method public androidx.constraintlayout.core.SolverVariable! getPivotCandidate(androidx.constraintlayout.core.LinearSystem!, boolean[]!);
+ method public void initFromRow(androidx.constraintlayout.core.LinearSystem.Row!);
+ method public boolean isEmpty();
+ method public androidx.constraintlayout.core.SolverVariable! pickPivot(androidx.constraintlayout.core.SolverVariable!);
+ method public void reset();
+ method public void updateFromFinalVariable(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.SolverVariable!, boolean);
+ method public void updateFromRow(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.ArrayRow!, boolean);
+ method public void updateFromSynonymVariable(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.SolverVariable!, boolean);
+ method public void updateFromSystem(androidx.constraintlayout.core.LinearSystem!);
+ field public androidx.constraintlayout.core.ArrayRow.ArrayRowVariables! variables;
+ }
+
+ public static interface ArrayRow.ArrayRowVariables {
+ method public void add(androidx.constraintlayout.core.SolverVariable!, float, boolean);
+ method public void clear();
+ method public boolean contains(androidx.constraintlayout.core.SolverVariable!);
+ method public void display();
+ method public void divideByAmount(float);
+ method public float get(androidx.constraintlayout.core.SolverVariable!);
+ method public int getCurrentSize();
+ method public androidx.constraintlayout.core.SolverVariable! getVariable(int);
+ method public float getVariableValue(int);
+ method public int indexOf(androidx.constraintlayout.core.SolverVariable!);
+ method public void invert();
+ method public void put(androidx.constraintlayout.core.SolverVariable!, float);
+ method public float remove(androidx.constraintlayout.core.SolverVariable!, boolean);
+ method public int sizeInBytes();
+ method public float use(androidx.constraintlayout.core.ArrayRow!, boolean);
+ }
+
+ public class Cache {
+ ctor public Cache();
+ }
+
+ public class GoalRow extends androidx.constraintlayout.core.ArrayRow {
+ ctor public GoalRow(androidx.constraintlayout.core.Cache!);
+ }
+
+ public class LinearSystem {
+ ctor public LinearSystem();
+ method public void addCenterPoint(androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintWidget!, float, int);
+ method public void addCentering(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, float, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, int);
+ method public void addConstraint(androidx.constraintlayout.core.ArrayRow!);
+ method public androidx.constraintlayout.core.ArrayRow! addEquality(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, int);
+ method public void addEquality(androidx.constraintlayout.core.SolverVariable!, int);
+ method public void addGreaterBarrier(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, boolean);
+ method public void addGreaterThan(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, int);
+ method public void addLowerBarrier(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, boolean);
+ method public void addLowerThan(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int, int);
+ method public void addRatio(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, float, int);
+ method public void addSynonym(androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, int);
+ method public androidx.constraintlayout.core.SolverVariable! createErrorVariable(int, String!);
+ method public androidx.constraintlayout.core.SolverVariable! createExtraVariable();
+ method public androidx.constraintlayout.core.SolverVariable! createObjectVariable(Object!);
+ method public androidx.constraintlayout.core.ArrayRow! createRow();
+ method public static androidx.constraintlayout.core.ArrayRow! createRowDimensionPercent(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.SolverVariable!, androidx.constraintlayout.core.SolverVariable!, float);
+ method public androidx.constraintlayout.core.SolverVariable! createSlackVariable();
+ method public void displayReadableRows();
+ method public void displayVariablesReadableRows();
+ method public void fillMetrics(androidx.constraintlayout.core.Metrics!);
+ method public androidx.constraintlayout.core.Cache! getCache();
+ method public int getMemoryUsed();
+ method public static androidx.constraintlayout.core.Metrics! getMetrics();
+ method public int getNumEquations();
+ method public int getNumVariables();
+ method public int getObjectVariableValue(Object!);
+ method public void minimize() throws java.lang.Exception;
+ method public void removeRow(androidx.constraintlayout.core.ArrayRow!);
+ method public void reset();
+ field public static long ARRAY_ROW_CREATION;
+ field public static final boolean DEBUG = false;
+ field public static final boolean FULL_DEBUG = false;
+ field public static long OPTIMIZED_ARRAY_ROW_CREATION;
+ field public static boolean OPTIMIZED_ENGINE;
+ field public static boolean SIMPLIFY_SYNONYMS;
+ field public static boolean SKIP_COLUMNS;
+ field public static boolean USE_BASIC_SYNONYMS;
+ field public static boolean USE_DEPENDENCY_ORDERING;
+ field public static boolean USE_SYNONYMS;
+ field public boolean graphOptimizer;
+ field public boolean hasSimpleDefinition;
+ field public boolean newgraphOptimizer;
+ field public static androidx.constraintlayout.core.Metrics! sMetrics;
+ }
+
+ public class Metrics {
+ ctor public Metrics();
+ method public void copy(androidx.constraintlayout.core.Metrics!);
+ method public void reset();
+ field public long additionalMeasures;
+ field public long bfs;
+ field public long constraints;
+ field public long determineGroups;
+ field public long errors;
+ field public long extravariables;
+ field public long fullySolved;
+ field public long graphOptimizer;
+ field public long graphSolved;
+ field public long grouping;
+ field public long infeasibleDetermineGroups;
+ field public long iterations;
+ field public long lastTableSize;
+ field public long layouts;
+ field public long linearSolved;
+ field public long mChildCount;
+ field public long mEquations;
+ field public long mMeasureCalls;
+ field public long mMeasureDuration;
+ field public int mNumberOfLayouts;
+ field public int mNumberOfMeasures;
+ field public long mSimpleEquations;
+ field public long mSolverPasses;
+ field public long mVariables;
+ field public long maxRows;
+ field public long maxTableSize;
+ field public long maxVariables;
+ field public long measuredMatchWidgets;
+ field public long measuredWidgets;
+ field public long measures;
+ field public long measuresLayoutDuration;
+ field public long measuresWidgetsDuration;
+ field public long measuresWrap;
+ field public long measuresWrapInfeasible;
+ field public long minimize;
+ field public long minimizeGoal;
+ field public long nonresolvedWidgets;
+ field public long optimize;
+ field public long pivots;
+ field public java.util.ArrayList<java.lang.String!>! problematicLayouts;
+ field public long resolutions;
+ field public long resolvedWidgets;
+ field public long simpleconstraints;
+ field public long slackvariables;
+ field public long tableSizeIncrease;
+ field public long variables;
+ field public long widgets;
+ }
+
+ public class PriorityGoalRow extends androidx.constraintlayout.core.ArrayRow {
+ ctor public PriorityGoalRow(androidx.constraintlayout.core.Cache!);
+ }
+
+ public class SolverVariable implements java.lang.Comparable<androidx.constraintlayout.core.SolverVariable!> {
+ ctor public SolverVariable(androidx.constraintlayout.core.SolverVariable.Type!, String!);
+ ctor public SolverVariable(String!, androidx.constraintlayout.core.SolverVariable.Type!);
+ method public final void addToRow(androidx.constraintlayout.core.ArrayRow!);
+ method public int compareTo(androidx.constraintlayout.core.SolverVariable!);
+ method public String! getName();
+ method public final void removeFromRow(androidx.constraintlayout.core.ArrayRow!);
+ method public void reset();
+ method public void setFinalValue(androidx.constraintlayout.core.LinearSystem!, float);
+ method public void setName(String!);
+ method public void setSynonym(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.SolverVariable!, float);
+ method public void setType(androidx.constraintlayout.core.SolverVariable.Type!, String!);
+ method public final void updateReferencesWithNewDefinition(androidx.constraintlayout.core.LinearSystem!, androidx.constraintlayout.core.ArrayRow!);
+ field public static final int STRENGTH_BARRIER = 6; // 0x6
+ field public static final int STRENGTH_CENTERING = 7; // 0x7
+ field public static final int STRENGTH_EQUALITY = 5; // 0x5
+ field public static final int STRENGTH_FIXED = 8; // 0x8
+ field public static final int STRENGTH_HIGH = 3; // 0x3
+ field public static final int STRENGTH_HIGHEST = 4; // 0x4
+ field public static final int STRENGTH_LOW = 1; // 0x1
+ field public static final int STRENGTH_MEDIUM = 2; // 0x2
+ field public static final int STRENGTH_NONE = 0; // 0x0
+ field public float computedValue;
+ field public int id;
+ field public boolean inGoal;
+ field public boolean isFinalValue;
+ field public int strength;
+ field public int usageInRowCount;
+ }
+
+ public enum SolverVariable.Type {
+ enum_constant public static final androidx.constraintlayout.core.SolverVariable.Type CONSTANT;
+ enum_constant public static final androidx.constraintlayout.core.SolverVariable.Type ERROR;
+ enum_constant public static final androidx.constraintlayout.core.SolverVariable.Type SLACK;
+ enum_constant public static final androidx.constraintlayout.core.SolverVariable.Type UNKNOWN;
+ enum_constant public static final androidx.constraintlayout.core.SolverVariable.Type UNRESTRICTED;
+ }
+
+ public class SolverVariableValues implements androidx.constraintlayout.core.ArrayRow.ArrayRowVariables {
+ method public void add(androidx.constraintlayout.core.SolverVariable!, float, boolean);
+ method public void clear();
+ method public boolean contains(androidx.constraintlayout.core.SolverVariable!);
+ method public void display();
+ method public void divideByAmount(float);
+ method public float get(androidx.constraintlayout.core.SolverVariable!);
+ method public int getCurrentSize();
+ method public androidx.constraintlayout.core.SolverVariable! getVariable(int);
+ method public float getVariableValue(int);
+ method public int indexOf(androidx.constraintlayout.core.SolverVariable!);
+ method public void invert();
+ method public void put(androidx.constraintlayout.core.SolverVariable!, float);
+ method public float remove(androidx.constraintlayout.core.SolverVariable!, boolean);
+ method public int sizeInBytes();
+ method public float use(androidx.constraintlayout.core.ArrayRow!, boolean);
+ field protected final androidx.constraintlayout.core.Cache! mCache;
+ }
+
+}
+
+package androidx.constraintlayout.core.dsl {
+
+ public class Barrier extends androidx.constraintlayout.core.dsl.Helper {
+ ctor public Barrier(String!);
+ ctor public Barrier(String!, String!);
+ method public androidx.constraintlayout.core.dsl.Barrier! addReference(androidx.constraintlayout.core.dsl.Ref!);
+ method public androidx.constraintlayout.core.dsl.Barrier! addReference(String!);
+ method public androidx.constraintlayout.core.dsl.Constraint.Side! getDirection();
+ method public int getMargin();
+ method public String! referencesToString();
+ method public void setDirection(androidx.constraintlayout.core.dsl.Constraint.Side!);
+ method public void setMargin(int);
+ }
+
+ public abstract class Chain extends androidx.constraintlayout.core.dsl.Helper {
+ ctor public Chain(String!);
+ method public androidx.constraintlayout.core.dsl.Chain! addReference(androidx.constraintlayout.core.dsl.Ref!);
+ method public androidx.constraintlayout.core.dsl.Chain! addReference(String!);
+ method public androidx.constraintlayout.core.dsl.Chain.Style! getStyle();
+ method public String! referencesToString();
+ method public void setStyle(androidx.constraintlayout.core.dsl.Chain.Style!);
+ field protected java.util.ArrayList<androidx.constraintlayout.core.dsl.Ref!>! references;
+ field protected static final java.util.Map<androidx.constraintlayout.core.dsl.Chain.Style!,java.lang.String!>! styleMap;
+ }
+
+ public class Chain.Anchor {
+ method public void build(StringBuilder!);
+ method public String! getId();
+ }
+
+ public enum Chain.Style {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Chain.Style PACKED;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Chain.Style SPREAD;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Chain.Style SPREAD_INSIDE;
+ }
+
+ public class Constraint {
+ ctor public Constraint(String!);
+ method protected void append(StringBuilder!, String!, float);
+ method public String! convertStringArrayToString(String![]!);
+ method public androidx.constraintlayout.core.dsl.Constraint.VAnchor! getBaseline();
+ method public androidx.constraintlayout.core.dsl.Constraint.VAnchor! getBottom();
+ method public float getCircleAngle();
+ method public String! getCircleConstraint();
+ method public int getCircleRadius();
+ method public String! getDimensionRatio();
+ method public int getEditorAbsoluteX();
+ method public int getEditorAbsoluteY();
+ method public androidx.constraintlayout.core.dsl.Constraint.HAnchor! getEnd();
+ method public int getHeight();
+ method public androidx.constraintlayout.core.dsl.Constraint.Behaviour! getHeightDefault();
+ method public int getHeightMax();
+ method public int getHeightMin();
+ method public float getHeightPercent();
+ method public float getHorizontalBias();
+ method public androidx.constraintlayout.core.dsl.Constraint.ChainMode! getHorizontalChainStyle();
+ method public float getHorizontalWeight();
+ method public androidx.constraintlayout.core.dsl.Constraint.HAnchor! getLeft();
+ method public String![]! getReferenceIds();
+ method public androidx.constraintlayout.core.dsl.Constraint.HAnchor! getRight();
+ method public androidx.constraintlayout.core.dsl.Constraint.HAnchor! getStart();
+ method public androidx.constraintlayout.core.dsl.Constraint.VAnchor! getTop();
+ method public float getVerticalBias();
+ method public androidx.constraintlayout.core.dsl.Constraint.ChainMode! getVerticalChainStyle();
+ method public float getVerticalWeight();
+ method public int getWidth();
+ method public androidx.constraintlayout.core.dsl.Constraint.Behaviour! getWidthDefault();
+ method public int getWidthMax();
+ method public int getWidthMin();
+ method public float getWidthPercent();
+ method public boolean isConstrainedHeight();
+ method public boolean isConstrainedWidth();
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ method public void setCircleAngle(float);
+ method public void setCircleConstraint(String!);
+ method public void setCircleRadius(int);
+ method public void setConstrainedHeight(boolean);
+ method public void setConstrainedWidth(boolean);
+ method public void setDimensionRatio(String!);
+ method public void setEditorAbsoluteX(int);
+ method public void setEditorAbsoluteY(int);
+ method public void setHeight(int);
+ method public void setHeightDefault(androidx.constraintlayout.core.dsl.Constraint.Behaviour!);
+ method public void setHeightMax(int);
+ method public void setHeightMin(int);
+ method public void setHeightPercent(float);
+ method public void setHorizontalBias(float);
+ method public void setHorizontalChainStyle(androidx.constraintlayout.core.dsl.Constraint.ChainMode!);
+ method public void setHorizontalWeight(float);
+ method public void setReferenceIds(String![]!);
+ method public void setVerticalBias(float);
+ method public void setVerticalChainStyle(androidx.constraintlayout.core.dsl.Constraint.ChainMode!);
+ method public void setVerticalWeight(float);
+ method public void setWidth(int);
+ method public void setWidthDefault(androidx.constraintlayout.core.dsl.Constraint.Behaviour!);
+ method public void setWidthMax(int);
+ method public void setWidthMin(int);
+ method public void setWidthPercent(float);
+ field public static final androidx.constraintlayout.core.dsl.Constraint! PARENT;
+ }
+
+ public class Constraint.Anchor {
+ method public void build(StringBuilder!);
+ method public String! getId();
+ }
+
+ public enum Constraint.Behaviour {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Behaviour PERCENT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Behaviour RATIO;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Behaviour RESOLVED;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Behaviour SPREAD;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Behaviour WRAP;
+ }
+
+ public enum Constraint.ChainMode {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.ChainMode PACKED;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.ChainMode SPREAD;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.ChainMode SPREAD_INSIDE;
+ }
+
+ public class Constraint.HAnchor extends androidx.constraintlayout.core.dsl.Constraint.Anchor {
+ }
+
+ public enum Constraint.HSide {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.HSide END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.HSide LEFT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.HSide RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.HSide START;
+ }
+
+ public enum Constraint.Side {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side LEFT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side START;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.Side TOP;
+ }
+
+ public class Constraint.VAnchor extends androidx.constraintlayout.core.dsl.Constraint.Anchor {
+ }
+
+ public enum Constraint.VSide {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.VSide BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.VSide BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Constraint.VSide TOP;
+ }
+
+ public class ConstraintSet {
+ ctor public ConstraintSet(String!);
+ method public void add(androidx.constraintlayout.core.dsl.Constraint!);
+ method public void add(androidx.constraintlayout.core.dsl.Helper!);
+ }
+
+ public abstract class Guideline extends androidx.constraintlayout.core.dsl.Helper {
+ method public int getEnd();
+ method public float getPercent();
+ method public int getStart();
+ method public void setEnd(int);
+ method public void setPercent(float);
+ method public void setStart(int);
+ }
+
+ public class HChain extends androidx.constraintlayout.core.dsl.Chain {
+ ctor public HChain(String!);
+ ctor public HChain(String!, String!);
+ method public androidx.constraintlayout.core.dsl.HChain.HAnchor! getEnd();
+ method public androidx.constraintlayout.core.dsl.HChain.HAnchor! getLeft();
+ method public androidx.constraintlayout.core.dsl.HChain.HAnchor! getRight();
+ method public androidx.constraintlayout.core.dsl.HChain.HAnchor! getStart();
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToEnd(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToLeft(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToRight(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int);
+ method public void linkToStart(androidx.constraintlayout.core.dsl.Constraint.HAnchor!, int, int);
+ }
+
+ public class HChain.HAnchor extends androidx.constraintlayout.core.dsl.Chain.Anchor {
+ }
+
+ public class Helper {
+ ctor public Helper(String!, androidx.constraintlayout.core.dsl.Helper.HelperType!);
+ ctor public Helper(String!, androidx.constraintlayout.core.dsl.Helper.HelperType!, String!);
+ method public void append(java.util.Map<java.lang.String!,java.lang.String!>!, StringBuilder!);
+ method public java.util.Map<java.lang.String!,java.lang.String!>! convertConfigToMap();
+ method public String! getConfig();
+ method public String! getId();
+ method public androidx.constraintlayout.core.dsl.Helper.HelperType! getType();
+ method public static void main(String![]!);
+ field protected String! config;
+ field protected java.util.Map<java.lang.String!,java.lang.String!>! configMap;
+ field protected final String! name;
+ field protected static final java.util.Map<androidx.constraintlayout.core.dsl.Constraint.Side!,java.lang.String!>! sideMap;
+ field protected androidx.constraintlayout.core.dsl.Helper.HelperType! type;
+ field protected static final java.util.Map<androidx.constraintlayout.core.dsl.Helper.Type!,java.lang.String!>! typeMap;
+ }
+
+ public static final class Helper.HelperType {
+ ctor public Helper.HelperType(String!);
+ }
+
+ public enum Helper.Type {
+ enum_constant public static final androidx.constraintlayout.core.dsl.Helper.Type BARRIER;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Helper.Type HORIZONTAL_CHAIN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Helper.Type HORIZONTAL_GUIDELINE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Helper.Type VERTICAL_CHAIN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.Helper.Type VERTICAL_GUIDELINE;
+ }
+
+ public class KeyAttribute extends androidx.constraintlayout.core.dsl.Keys {
+ ctor public KeyAttribute(int, String!);
+ method protected void attributesToString(StringBuilder!);
+ method public float getAlpha();
+ method public androidx.constraintlayout.core.dsl.KeyAttribute.Fit! getCurveFit();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public String! getTarget();
+ method public String! getTransitionEasing();
+ method public float getTransitionPathRotate();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public androidx.constraintlayout.core.dsl.KeyAttribute.Visibility! getVisibility();
+ method public void setAlpha(float);
+ method public void setCurveFit(androidx.constraintlayout.core.dsl.KeyAttribute.Fit!);
+ method public void setPivotX(float);
+ method public void setPivotY(float);
+ method public void setRotation(float);
+ method public void setRotationX(float);
+ method public void setRotationY(float);
+ method public void setScaleX(float);
+ method public void setScaleY(float);
+ method public void setTarget(String!);
+ method public void setTransitionEasing(String!);
+ method public void setTransitionPathRotate(float);
+ method public void setTranslationX(float);
+ method public void setTranslationY(float);
+ method public void setTranslationZ(float);
+ method public void setVisibility(androidx.constraintlayout.core.dsl.KeyAttribute.Visibility!);
+ field protected String! TYPE;
+ }
+
+ public enum KeyAttribute.Fit {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttribute.Fit LINEAR;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttribute.Fit SPLINE;
+ }
+
+ public enum KeyAttribute.Visibility {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttribute.Visibility GONE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttribute.Visibility INVISIBLE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttribute.Visibility VISIBLE;
+ }
+
+ public class KeyAttributes extends androidx.constraintlayout.core.dsl.Keys {
+ method protected void attributesToString(StringBuilder!);
+ method public float[]! getAlpha();
+ method public androidx.constraintlayout.core.dsl.KeyAttributes.Fit! getCurveFit();
+ method public float[]! getPivotX();
+ method public float[]! getPivotY();
+ method public float[]! getRotation();
+ method public float[]! getRotationX();
+ method public float[]! getRotationY();
+ method public float[]! getScaleX();
+ method public float[]! getScaleY();
+ method public String![]! getTarget();
+ method public String! getTransitionEasing();
+ method public float[]! getTransitionPathRotate();
+ method public float[]! getTranslationX();
+ method public float[]! getTranslationY();
+ method public float[]! getTranslationZ();
+ method public androidx.constraintlayout.core.dsl.KeyAttributes.Visibility![]! getVisibility();
+ method public void setAlpha(float...!);
+ method public void setCurveFit(androidx.constraintlayout.core.dsl.KeyAttributes.Fit!);
+ method public void setPivotX(float...!);
+ method public void setPivotY(float...!);
+ method public void setRotation(float...!);
+ method public void setRotationX(float...!);
+ method public void setRotationY(float...!);
+ method public void setScaleX(float[]!);
+ method public void setScaleY(float[]!);
+ method public void setTarget(String![]!);
+ method public void setTransitionEasing(String!);
+ method public void setTransitionPathRotate(float...!);
+ method public void setTranslationX(float[]!);
+ method public void setTranslationY(float[]!);
+ method public void setTranslationZ(float[]!);
+ method public void setVisibility(androidx.constraintlayout.core.dsl.KeyAttributes.Visibility!...!);
+ field protected String! TYPE;
+ }
+
+ public enum KeyAttributes.Fit {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttributes.Fit LINEAR;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttributes.Fit SPLINE;
+ }
+
+ public enum KeyAttributes.Visibility {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttributes.Visibility GONE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttributes.Visibility INVISIBLE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyAttributes.Visibility VISIBLE;
+ }
+
+ public class KeyCycle extends androidx.constraintlayout.core.dsl.KeyAttribute {
+ method public float getOffset();
+ method public float getPeriod();
+ method public float getPhase();
+ method public androidx.constraintlayout.core.dsl.KeyCycle.Wave! getShape();
+ method public void setOffset(float);
+ method public void setPeriod(float);
+ method public void setPhase(float);
+ method public void setShape(androidx.constraintlayout.core.dsl.KeyCycle.Wave!);
+ }
+
+ public enum KeyCycle.Wave {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave COS;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave REVERSE_SAW;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave SAW;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave SIN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave SQUARE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycle.Wave TRIANGLE;
+ }
+
+ public class KeyCycles extends androidx.constraintlayout.core.dsl.KeyAttributes {
+ method public float[]! getWaveOffset();
+ method public float[]! getWavePeriod();
+ method public float[]! getWavePhase();
+ method public androidx.constraintlayout.core.dsl.KeyCycles.Wave! getWaveShape();
+ method public void setWaveOffset(float...!);
+ method public void setWavePeriod(float...!);
+ method public void setWavePhase(float...!);
+ method public void setWaveShape(androidx.constraintlayout.core.dsl.KeyCycles.Wave!);
+ }
+
+ public enum KeyCycles.Wave {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave COS;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave REVERSE_SAW;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave SAW;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave SIN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave SQUARE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyCycles.Wave TRIANGLE;
+ }
+
+ public class KeyFrames {
+ ctor public KeyFrames();
+ method public void add(androidx.constraintlayout.core.dsl.Keys!);
+ }
+
+ public class KeyPosition extends androidx.constraintlayout.core.dsl.Keys {
+ ctor public KeyPosition(String!, int);
+ method public int getFrames();
+ method public float getPercentHeight();
+ method public float getPercentWidth();
+ method public float getPercentX();
+ method public float getPercentY();
+ method public androidx.constraintlayout.core.dsl.KeyPosition.Type! getPositionType();
+ method public String! getTarget();
+ method public String! getTransitionEasing();
+ method public void setFrames(int);
+ method public void setPercentHeight(float);
+ method public void setPercentWidth(float);
+ method public void setPercentX(float);
+ method public void setPercentY(float);
+ method public void setPositionType(androidx.constraintlayout.core.dsl.KeyPosition.Type!);
+ method public void setTarget(String!);
+ method public void setTransitionEasing(String!);
+ }
+
+ public enum KeyPosition.Type {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPosition.Type CARTESIAN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPosition.Type PATH;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPosition.Type SCREEN;
+ }
+
+ public class KeyPositions extends androidx.constraintlayout.core.dsl.Keys {
+ ctor public KeyPositions(int, java.lang.String!...!);
+ method public int[]! getFrames();
+ method public float[]! getPercentHeight();
+ method public float[]! getPercentWidth();
+ method public float[]! getPercentX();
+ method public float[]! getPercentY();
+ method public androidx.constraintlayout.core.dsl.KeyPositions.Type! getPositionType();
+ method public String![]! getTarget();
+ method public String! getTransitionEasing();
+ method public void setFrames(int...!);
+ method public void setPercentHeight(float...!);
+ method public void setPercentWidth(float...!);
+ method public void setPercentX(float...!);
+ method public void setPercentY(float...!);
+ method public void setPositionType(androidx.constraintlayout.core.dsl.KeyPositions.Type!);
+ method public void setTransitionEasing(String!);
+ }
+
+ public enum KeyPositions.Type {
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPositions.Type CARTESIAN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPositions.Type PATH;
+ enum_constant public static final androidx.constraintlayout.core.dsl.KeyPositions.Type SCREEN;
+ }
+
+ public class Keys {
+ ctor public Keys();
+ method protected void append(StringBuilder!, String!, float);
+ method protected void append(StringBuilder!, String!, float[]!);
+ method protected void append(StringBuilder!, String!, int);
+ method protected void append(StringBuilder!, String!, String!);
+ method protected void append(StringBuilder!, String!, String![]!);
+ method protected String! unpack(String![]!);
+ }
+
+ public class MotionScene {
+ ctor public MotionScene();
+ method public void addConstraintSet(androidx.constraintlayout.core.dsl.ConstraintSet!);
+ method public void addTransition(androidx.constraintlayout.core.dsl.Transition!);
+ }
+
+ public class OnSwipe {
+ ctor public OnSwipe();
+ ctor public OnSwipe(String!, androidx.constraintlayout.core.dsl.OnSwipe.Side!, androidx.constraintlayout.core.dsl.OnSwipe.Drag!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe.Mode! getAutoCompleteMode();
+ method public androidx.constraintlayout.core.dsl.OnSwipe.Drag! getDragDirection();
+ method public float getDragScale();
+ method public float getDragThreshold();
+ method public String! getLimitBoundsTo();
+ method public float getMaxAcceleration();
+ method public float getMaxVelocity();
+ method public androidx.constraintlayout.core.dsl.OnSwipe.TouchUp! getOnTouchUp();
+ method public String! getRotationCenterId();
+ method public androidx.constraintlayout.core.dsl.OnSwipe.Boundary! getSpringBoundary();
+ method public float getSpringDamping();
+ method public float getSpringMass();
+ method public float getSpringStiffness();
+ method public float getSpringStopThreshold();
+ method public String! getTouchAnchorId();
+ method public androidx.constraintlayout.core.dsl.OnSwipe.Side! getTouchAnchorSide();
+ method public void setAutoCompleteMode(androidx.constraintlayout.core.dsl.OnSwipe.Mode!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setDragDirection(androidx.constraintlayout.core.dsl.OnSwipe.Drag!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setDragScale(int);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setDragThreshold(int);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setLimitBoundsTo(String!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setMaxAcceleration(int);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setMaxVelocity(int);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setOnTouchUp(androidx.constraintlayout.core.dsl.OnSwipe.TouchUp!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setRotateCenter(String!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setSpringBoundary(androidx.constraintlayout.core.dsl.OnSwipe.Boundary!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setSpringDamping(float);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setSpringMass(float);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setSpringStiffness(float);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setSpringStopThreshold(float);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setTouchAnchorId(String!);
+ method public androidx.constraintlayout.core.dsl.OnSwipe! setTouchAnchorSide(androidx.constraintlayout.core.dsl.OnSwipe.Side!);
+ field public static final int FLAG_DISABLE_POST_SCROLL = 1; // 0x1
+ field public static final int FLAG_DISABLE_SCROLL = 2; // 0x2
+ }
+
+ public enum OnSwipe.Boundary {
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Boundary BOUNCE_BOTH;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Boundary BOUNCE_END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Boundary BOUNCE_START;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Boundary OVERSHOOT;
+ }
+
+ public enum OnSwipe.Drag {
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag ANTICLOCKWISE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag CLOCKWISE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag DOWN;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag LEFT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag START;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Drag UP;
+ }
+
+ public enum OnSwipe.Mode {
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Mode SPRING;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Mode VELOCITY;
+ }
+
+ public enum OnSwipe.Side {
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side LEFT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side MIDDLE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side START;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.Side TOP;
+ }
+
+ public enum OnSwipe.TouchUp {
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp AUTOCOMPLETE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp DECELERATE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp DECELERATE_COMPLETE;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp NEVER_COMPLETE_END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp NEVER_COMPLETE_START;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp STOP;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp TO_END;
+ enum_constant public static final androidx.constraintlayout.core.dsl.OnSwipe.TouchUp TO_START;
+ }
+
+ public class Ref {
+ method public static void addStringToReferences(String!, java.util.ArrayList<androidx.constraintlayout.core.dsl.Ref!>!);
+ method public String! getId();
+ method public float getPostMargin();
+ method public float getPreMargin();
+ method public float getWeight();
+ method public static float parseFloat(Object!);
+ method public static androidx.constraintlayout.core.dsl.Ref! parseStringToRef(String!);
+ method public void setId(String!);
+ method public void setPostMargin(float);
+ method public void setPreMargin(float);
+ method public void setWeight(float);
+ }
+
+ public class Transition {
+ ctor public Transition(String!, String!);
+ ctor public Transition(String!, String!, String!);
+ method public String! getId();
+ method public void setDuration(int);
+ method public void setFrom(String!);
+ method public void setId(String!);
+ method public void setKeyFrames(androidx.constraintlayout.core.dsl.Keys!);
+ method public void setOnSwipe(androidx.constraintlayout.core.dsl.OnSwipe!);
+ method public void setStagger(float);
+ method public void setTo(String!);
+ }
+
+ public class VChain extends androidx.constraintlayout.core.dsl.Chain {
+ ctor public VChain(String!);
+ ctor public VChain(String!, String!);
+ method public androidx.constraintlayout.core.dsl.VChain.VAnchor! getBaseline();
+ method public androidx.constraintlayout.core.dsl.VChain.VAnchor! getBottom();
+ method public androidx.constraintlayout.core.dsl.VChain.VAnchor! getTop();
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToBaseline(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToBottom(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int);
+ method public void linkToTop(androidx.constraintlayout.core.dsl.Constraint.VAnchor!, int, int);
+ }
+
+ public class VChain.VAnchor extends androidx.constraintlayout.core.dsl.Chain.Anchor {
+ }
+
+ public class VGuideline extends androidx.constraintlayout.core.dsl.Guideline {
+ ctor public VGuideline(String!);
+ ctor public VGuideline(String!, String!);
+ }
+
+}
+
+package androidx.constraintlayout.core.motion {
+
+ public class CustomAttribute {
+ ctor public CustomAttribute(androidx.constraintlayout.core.motion.CustomAttribute!, Object!);
+ ctor public CustomAttribute(String!, androidx.constraintlayout.core.motion.CustomAttribute.AttributeType!);
+ ctor public CustomAttribute(String!, androidx.constraintlayout.core.motion.CustomAttribute.AttributeType!, Object!, boolean);
+ method public boolean diff(androidx.constraintlayout.core.motion.CustomAttribute!);
+ method public androidx.constraintlayout.core.motion.CustomAttribute.AttributeType! getType();
+ method public float getValueToInterpolate();
+ method public void getValuesToInterpolate(float[]!);
+ method public static int hsvToRgb(float, float, float);
+ method public boolean isContinuous();
+ method public int numberOfInterpolatedValues();
+ method public void setColorValue(int);
+ method public void setFloatValue(float);
+ method public void setIntValue(int);
+ method public void setStringValue(String!);
+ method public void setValue(float[]!);
+ method public void setValue(Object!);
+ }
+
+ public enum CustomAttribute.AttributeType {
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType BOOLEAN_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType COLOR_DRAWABLE_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType COLOR_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType DIMENSION_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType FLOAT_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType INT_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType REFERENCE_TYPE;
+ enum_constant public static final androidx.constraintlayout.core.motion.CustomAttribute.AttributeType STRING_TYPE;
+ }
+
+ public class CustomVariable {
+ ctor public CustomVariable(androidx.constraintlayout.core.motion.CustomVariable!);
+ ctor public CustomVariable(androidx.constraintlayout.core.motion.CustomVariable!, Object!);
+ ctor public CustomVariable(String!, int);
+ ctor public CustomVariable(String!, int, boolean);
+ ctor public CustomVariable(String!, int, float);
+ ctor public CustomVariable(String!, int, int);
+ ctor public CustomVariable(String!, int, Object!);
+ ctor public CustomVariable(String!, int, String!);
+ method public void applyToWidget(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public static String! colorString(int);
+ method public androidx.constraintlayout.core.motion.CustomVariable! copy();
+ method public boolean diff(androidx.constraintlayout.core.motion.CustomVariable!);
+ method public boolean getBooleanValue();
+ method public int getColorValue();
+ method public float getFloatValue();
+ method public int getIntegerValue();
+ method public int getInterpolatedColor(float[]!);
+ method public String! getName();
+ method public String! getStringValue();
+ method public int getType();
+ method public float getValueToInterpolate();
+ method public void getValuesToInterpolate(float[]!);
+ method public static int hsvToRgb(float, float, float);
+ method public boolean isContinuous();
+ method public int numberOfInterpolatedValues();
+ method public static int rgbaTocColor(float, float, float, float);
+ method public void setBooleanValue(boolean);
+ method public void setFloatValue(float);
+ method public void setIntValue(int);
+ method public void setInterpolatedValue(androidx.constraintlayout.core.motion.MotionWidget!, float[]!);
+ method public void setStringValue(String!);
+ method public void setValue(float[]!);
+ method public void setValue(Object!);
+ }
+
+ public class Motion implements androidx.constraintlayout.core.motion.utils.TypedValues {
+ ctor public Motion(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public void addKey(androidx.constraintlayout.core.motion.key.MotionKey!);
+ method public int buildKeyFrames(float[]!, int[]!, int[]!);
+ method public void buildPath(float[]!, int);
+ method public void buildRect(float, float[]!, int);
+ method public String! getAnimateRelativeTo();
+ method public void getCenter(double, float[]!, float[]!);
+ method public float getCenterX();
+ method public float getCenterY();
+ method public void getDpDt(float, float, float, float[]!);
+ method public int getDrawPath();
+ method public float getFinalHeight();
+ method public float getFinalWidth();
+ method public float getFinalX();
+ method public float getFinalY();
+ method public int getId(String!);
+ method public androidx.constraintlayout.core.motion.MotionPaths! getKeyFrame(int);
+ method public int getKeyFrameInfo(int, int[]!);
+ method public int getKeyFramePositions(int[]!, float[]!);
+ method public float getMotionStagger();
+ method public float getStartHeight();
+ method public float getStartWidth();
+ method public float getStartX();
+ method public float getStartY();
+ method public int getTransformPivotTarget();
+ method public androidx.constraintlayout.core.motion.MotionWidget! getView();
+ method public boolean interpolate(androidx.constraintlayout.core.motion.MotionWidget!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ method public void setDrawPath(int);
+ method public void setEnd(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public void setIdString(String!);
+ method public void setPathMotionArc(int);
+ method public void setStaggerOffset(float);
+ method public void setStaggerScale(float);
+ method public void setStart(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public void setStartState(androidx.constraintlayout.core.motion.utils.ViewState!, androidx.constraintlayout.core.motion.MotionWidget!, int, int, int);
+ method public void setTransformPivotTarget(int);
+ method public boolean setValue(int, boolean);
+ method public boolean setValue(int, float);
+ method public boolean setValue(int, int);
+ method public boolean setValue(int, String!);
+ method public void setView(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public void setup(int, int, float, long);
+ method public void setupRelative(androidx.constraintlayout.core.motion.Motion!);
+ field public static final int DRAW_PATH_AS_CONFIGURED = 4; // 0x4
+ field public static final int DRAW_PATH_BASIC = 1; // 0x1
+ field public static final int DRAW_PATH_CARTESIAN = 3; // 0x3
+ field public static final int DRAW_PATH_NONE = 0; // 0x0
+ field public static final int DRAW_PATH_RECTANGLE = 5; // 0x5
+ field public static final int DRAW_PATH_RELATIVE = 2; // 0x2
+ field public static final int DRAW_PATH_SCREEN = 6; // 0x6
+ field public static final int HORIZONTAL_PATH_X = 2; // 0x2
+ field public static final int HORIZONTAL_PATH_Y = 3; // 0x3
+ field public static final int PATH_PERCENT = 0; // 0x0
+ field public static final int PATH_PERPENDICULAR = 1; // 0x1
+ field public static final int ROTATION_LEFT = 2; // 0x2
+ field public static final int ROTATION_RIGHT = 1; // 0x1
+ field public static final int VERTICAL_PATH_X = 4; // 0x4
+ field public static final int VERTICAL_PATH_Y = 5; // 0x5
+ field public String! mId;
+ }
+
+ public class MotionPaths implements java.lang.Comparable<androidx.constraintlayout.core.motion.MotionPaths!> {
+ ctor public MotionPaths();
+ ctor public MotionPaths(int, int, androidx.constraintlayout.core.motion.key.MotionKeyPosition!, androidx.constraintlayout.core.motion.MotionPaths!, androidx.constraintlayout.core.motion.MotionPaths!);
+ method public void applyParameters(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public int compareTo(androidx.constraintlayout.core.motion.MotionPaths!);
+ method public void configureRelativeTo(androidx.constraintlayout.core.motion.Motion!);
+ method public void setupRelative(androidx.constraintlayout.core.motion.Motion!, androidx.constraintlayout.core.motion.MotionPaths!);
+ field public static final int CARTESIAN = 0; // 0x0
+ field public static final boolean DEBUG = false;
+ field public static final boolean OLD_WAY = false;
+ field public static final int PERPENDICULAR = 1; // 0x1
+ field public static final int SCREEN = 2; // 0x2
+ field public static final String TAG = "MotionPaths";
+ field public String! mId;
+ }
+
+ public class MotionWidget implements androidx.constraintlayout.core.motion.utils.TypedValues {
+ ctor public MotionWidget();
+ ctor public MotionWidget(androidx.constraintlayout.core.state.WidgetFrame!);
+ method public androidx.constraintlayout.core.motion.MotionWidget! findViewById(int);
+ method public float getAlpha();
+ method public int getBottom();
+ method public androidx.constraintlayout.core.motion.CustomVariable! getCustomAttribute(String!);
+ method public java.util.Set<java.lang.String!>! getCustomAttributeNames();
+ method public int getHeight();
+ method public int getId(String!);
+ method public int getLeft();
+ method public String! getName();
+ method public androidx.constraintlayout.core.motion.MotionWidget! getParent();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public int getRight();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getRotationZ();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getTop();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public float getValueAttributes(int);
+ method public int getVisibility();
+ method public androidx.constraintlayout.core.state.WidgetFrame! getWidgetFrame();
+ method public int getWidth();
+ method public int getX();
+ method public int getY();
+ method public void layout(int, int, int, int);
+ method public void setBounds(int, int, int, int);
+ method public void setCustomAttribute(String!, int, boolean);
+ method public void setCustomAttribute(String!, int, float);
+ method public void setCustomAttribute(String!, int, int);
+ method public void setCustomAttribute(String!, int, String!);
+ method public void setInterpolatedValue(androidx.constraintlayout.core.motion.CustomAttribute!, float[]!);
+ method public void setPivotX(float);
+ method public void setPivotY(float);
+ method public void setRotationX(float);
+ method public void setRotationY(float);
+ method public void setRotationZ(float);
+ method public void setScaleX(float);
+ method public void setScaleY(float);
+ method public void setTranslationX(float);
+ method public void setTranslationY(float);
+ method public void setTranslationZ(float);
+ method public boolean setValue(int, boolean);
+ method public boolean setValue(int, float);
+ method public boolean setValue(int, int);
+ method public boolean setValue(int, String!);
+ method public boolean setValueAttributes(int, float);
+ method public boolean setValueMotion(int, float);
+ method public boolean setValueMotion(int, int);
+ method public boolean setValueMotion(int, String!);
+ method public void setVisibility(int);
+ method public void updateMotion(androidx.constraintlayout.core.motion.utils.TypedValues!);
+ field public static final int FILL_PARENT = -1; // 0xffffffff
+ field public static final int GONE_UNSET = -2147483648; // 0x80000000
+ field public static final int INVISIBLE = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_WRAP = 1; // 0x1
+ field public static final int MATCH_PARENT = -1; // 0xffffffff
+ field public static final int PARENT_ID = 0; // 0x0
+ field public static final int ROTATE_LEFT_OF_PORTRATE = 4; // 0x4
+ field public static final int ROTATE_NONE = 0; // 0x0
+ field public static final int ROTATE_PORTRATE_OF_LEFT = 2; // 0x2
+ field public static final int ROTATE_PORTRATE_OF_RIGHT = 1; // 0x1
+ field public static final int ROTATE_RIGHT_OF_PORTRATE = 3; // 0x3
+ field public static final int UNSET = -1; // 0xffffffff
+ field public static final int VISIBILITY_MODE_IGNORE = 1; // 0x1
+ field public static final int VISIBILITY_MODE_NORMAL = 0; // 0x0
+ field public static final int VISIBLE = 4; // 0x4
+ field public static final int WRAP_CONTENT = -2; // 0xfffffffe
+ }
+
+ public static class MotionWidget.Motion {
+ ctor public MotionWidget.Motion();
+ field public int mAnimateCircleAngleTo;
+ field public String! mAnimateRelativeTo;
+ field public int mDrawPath;
+ field public float mMotionStagger;
+ field public int mPathMotionArc;
+ field public float mPathRotate;
+ field public int mPolarRelativeTo;
+ field public int mQuantizeInterpolatorID;
+ field public String! mQuantizeInterpolatorString;
+ field public int mQuantizeInterpolatorType;
+ field public float mQuantizeMotionPhase;
+ field public int mQuantizeMotionSteps;
+ field public String! mTransitionEasing;
+ }
+
+ public static class MotionWidget.PropertySet {
+ ctor public MotionWidget.PropertySet();
+ field public float alpha;
+ field public float mProgress;
+ field public int mVisibilityMode;
+ field public int visibility;
+ }
+
+}
+
+package androidx.constraintlayout.core.motion.key {
+
+ public class MotionConstraintSet {
+ ctor public MotionConstraintSet();
+ field public static final int ROTATE_LEFT_OF_PORTRATE = 4; // 0x4
+ field public static final int ROTATE_NONE = 0; // 0x0
+ field public static final int ROTATE_PORTRATE_OF_LEFT = 2; // 0x2
+ field public static final int ROTATE_PORTRATE_OF_RIGHT = 1; // 0x1
+ field public static final int ROTATE_RIGHT_OF_PORTRATE = 3; // 0x3
+ field public String! mIdString;
+ field public int mRotate;
+ }
+
+ public abstract class MotionKey implements androidx.constraintlayout.core.motion.utils.TypedValues {
+ ctor public MotionKey();
+ method public abstract void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public abstract androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public androidx.constraintlayout.core.motion.key.MotionKey! copy(androidx.constraintlayout.core.motion.key.MotionKey!);
+ method public abstract void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getFramePosition();
+ method public void setCustomAttribute(String!, int, boolean);
+ method public void setCustomAttribute(String!, int, float);
+ method public void setCustomAttribute(String!, int, int);
+ method public void setCustomAttribute(String!, int, String!);
+ method public void setFramePosition(int);
+ method public void setInterpolation(java.util.HashMap<java.lang.String!,java.lang.Integer!>!);
+ method public boolean setValue(int, boolean);
+ method public boolean setValue(int, float);
+ method public boolean setValue(int, int);
+ method public boolean setValue(int, String!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! setViewId(int);
+ field public static final String ALPHA = "alpha";
+ field public static final String CUSTOM = "CUSTOM";
+ field public static final String ELEVATION = "elevation";
+ field public static final String ROTATION = "rotationZ";
+ field public static final String ROTATION_X = "rotationX";
+ field public static final String SCALE_X = "scaleX";
+ field public static final String SCALE_Y = "scaleY";
+ field public static final String TRANSITION_PATH_ROTATE = "transitionPathRotate";
+ field public static final String TRANSLATION_X = "translationX";
+ field public static final String TRANSLATION_Y = "translationY";
+ field public static int UNSET;
+ field public static final String VISIBILITY = "visibility";
+ field public java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.CustomVariable!>! mCustom;
+ field public int mFramePosition;
+ field public int mType;
+ }
+
+ public class MotionKeyAttributes extends androidx.constraintlayout.core.motion.key.MotionKey {
+ ctor public MotionKeyAttributes();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getCurveFit();
+ method public int getId(String!);
+ method public void printAttributes();
+ field public static final int KEY_TYPE = 1; // 0x1
+ }
+
+ public class MotionKeyCycle extends androidx.constraintlayout.core.motion.key.MotionKey {
+ ctor public MotionKeyCycle();
+ method public void addCycleValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!>!);
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public void dump();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getId(String!);
+ method public float getValue(String!);
+ method public void printAttributes();
+ field public static final int KEY_TYPE = 4; // 0x4
+ field public static final int SHAPE_BOUNCE = 6; // 0x6
+ field public static final int SHAPE_COS_WAVE = 5; // 0x5
+ field public static final int SHAPE_REVERSE_SAW_WAVE = 4; // 0x4
+ field public static final int SHAPE_SAW_WAVE = 3; // 0x3
+ field public static final int SHAPE_SIN_WAVE = 0; // 0x0
+ field public static final int SHAPE_SQUARE_WAVE = 1; // 0x1
+ field public static final int SHAPE_TRIANGLE_WAVE = 2; // 0x2
+ field public static final String WAVE_OFFSET = "waveOffset";
+ field public static final String WAVE_PERIOD = "wavePeriod";
+ field public static final String WAVE_PHASE = "wavePhase";
+ field public static final String WAVE_SHAPE = "waveShape";
+ }
+
+ public class MotionKeyPosition extends androidx.constraintlayout.core.motion.key.MotionKey {
+ ctor public MotionKeyPosition();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getId(String!);
+ method public boolean intersects(int, int, androidx.constraintlayout.core.motion.utils.FloatRect!, androidx.constraintlayout.core.motion.utils.FloatRect!, float, float);
+ method public void positionAttributes(androidx.constraintlayout.core.motion.MotionWidget!, androidx.constraintlayout.core.motion.utils.FloatRect!, androidx.constraintlayout.core.motion.utils.FloatRect!, float, float, String![]!, float[]!);
+ field protected static final float SELECTION_SLOPE = 20.0f;
+ field public static final int TYPE_CARTESIAN = 0; // 0x0
+ field public static final int TYPE_PATH = 1; // 0x1
+ field public static final int TYPE_SCREEN = 2; // 0x2
+ field public float mAltPercentX;
+ field public float mAltPercentY;
+ field public int mCurveFit;
+ field public int mDrawPath;
+ field public int mPathMotionArc;
+ field public float mPercentHeight;
+ field public float mPercentWidth;
+ field public float mPercentX;
+ field public float mPercentY;
+ field public int mPositionType;
+ field public String! mTransitionEasing;
+ }
+
+ public class MotionKeyTimeCycle extends androidx.constraintlayout.core.motion.key.MotionKey {
+ ctor public MotionKeyTimeCycle();
+ method public void addTimeValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet!>!);
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public androidx.constraintlayout.core.motion.key.MotionKeyTimeCycle! copy(androidx.constraintlayout.core.motion.key.MotionKey!);
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getId(String!);
+ field public static final int KEY_TYPE = 3; // 0x3
+ }
+
+ public class MotionKeyTrigger extends androidx.constraintlayout.core.motion.key.MotionKey {
+ ctor public MotionKeyTrigger();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.core.motion.utils.SplineSet!>!);
+ method public androidx.constraintlayout.core.motion.key.MotionKey! clone();
+ method public void conditionallyFire(float, androidx.constraintlayout.core.motion.MotionWidget!);
+ method public androidx.constraintlayout.core.motion.key.MotionKeyTrigger! copy(androidx.constraintlayout.core.motion.key.MotionKey!);
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public int getId(String!);
+ field public static final String CROSS = "CROSS";
+ field public static final int KEY_TYPE = 5; // 0x5
+ field public static final String NEGATIVE_CROSS = "negativeCross";
+ field public static final String POSITIVE_CROSS = "positiveCross";
+ field public static final String POST_LAYOUT = "postLayout";
+ field public static final String TRIGGER_COLLISION_ID = "triggerCollisionId";
+ field public static final String TRIGGER_COLLISION_VIEW = "triggerCollisionView";
+ field public static final String TRIGGER_ID = "triggerID";
+ field public static final String TRIGGER_RECEIVER = "triggerReceiver";
+ field public static final String TRIGGER_SLACK = "triggerSlack";
+ field public static final int TYPE_CROSS = 312; // 0x138
+ field public static final int TYPE_NEGATIVE_CROSS = 310; // 0x136
+ field public static final int TYPE_POSITIVE_CROSS = 309; // 0x135
+ field public static final int TYPE_POST_LAYOUT = 304; // 0x130
+ field public static final int TYPE_TRIGGER_COLLISION_ID = 307; // 0x133
+ field public static final int TYPE_TRIGGER_COLLISION_VIEW = 306; // 0x132
+ field public static final int TYPE_TRIGGER_ID = 308; // 0x134
+ field public static final int TYPE_TRIGGER_RECEIVER = 311; // 0x137
+ field public static final int TYPE_TRIGGER_SLACK = 305; // 0x131
+ field public static final int TYPE_VIEW_TRANSITION_ON_CROSS = 301; // 0x12d
+ field public static final int TYPE_VIEW_TRANSITION_ON_NEGATIVE_CROSS = 303; // 0x12f
+ field public static final int TYPE_VIEW_TRANSITION_ON_POSITIVE_CROSS = 302; // 0x12e
+ field public static final String VIEW_TRANSITION_ON_CROSS = "viewTransitionOnCross";
+ field public static final String VIEW_TRANSITION_ON_NEGATIVE_CROSS = "viewTransitionOnNegativeCross";
+ field public static final String VIEW_TRANSITION_ON_POSITIVE_CROSS = "viewTransitionOnPositiveCross";
+ }
+
+}
+
+package androidx.constraintlayout.core.motion.parse {
+
+ public class KeyParser {
+ ctor public KeyParser();
+ method public static void main(String![]!);
+ method public static androidx.constraintlayout.core.motion.utils.TypedBundle! parseAttributes(String!);
+ }
+
+}
+
+package androidx.constraintlayout.core.motion.utils {
+
+ public class ArcCurveFit extends androidx.constraintlayout.core.motion.utils.CurveFit {
+ ctor public ArcCurveFit(int[]!, double[]!, double[]![]!);
+ method public void getPos(double, double[]!);
+ method public void getPos(double, float[]!);
+ method public double getPos(double, int);
+ method public void getSlope(double, double[]!);
+ method public double getSlope(double, int);
+ method public double[]! getTimePoints();
+ field public static final int ARC_ABOVE = 5; // 0x5
+ field public static final int ARC_BELOW = 4; // 0x4
+ field public static final int ARC_START_FLIP = 3; // 0x3
+ field public static final int ARC_START_HORIZONTAL = 2; // 0x2
+ field public static final int ARC_START_LINEAR = 0; // 0x0
+ field public static final int ARC_START_VERTICAL = 1; // 0x1
+ }
+
+ public abstract class CurveFit {
+ ctor public CurveFit();
+ method public static androidx.constraintlayout.core.motion.utils.CurveFit! get(int, double[]!, double[]![]!);
+ method public static androidx.constraintlayout.core.motion.utils.CurveFit! getArc(int[]!, double[]!, double[]![]!);
+ method public abstract void getPos(double, double[]!);
+ method public abstract void getPos(double, float[]!);
+ method public abstract double getPos(double, int);
+ method public abstract void getSlope(double, double[]!);
+ method public abstract double getSlope(double, int);
+ method public abstract double[]! getTimePoints();
+ field public static final int CONSTANT = 2; // 0x2
+ field public static final int LINEAR = 1; // 0x1
+ field public static final int SPLINE = 0; // 0x0
+ }
+
+ public interface DifferentialInterpolator {
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ }
+
+ public class Easing {
+ ctor public Easing();
+ method public double get(double);
+ method public double getDiff(double);
+ method public static androidx.constraintlayout.core.motion.utils.Easing! getInterpolator(String!);
+ field public static String![]! NAMED_EASING;
+ }
+
+ public class FloatRect {
+ ctor public FloatRect();
+ method public final float centerX();
+ method public final float centerY();
+ field public float bottom;
+ field public float left;
+ field public float right;
+ field public float top;
+ }
+
+ public class HyperSpline {
+ ctor public HyperSpline();
+ ctor public HyperSpline(double[]![]!);
+ method public double approxLength(androidx.constraintlayout.core.motion.utils.HyperSpline.Cubic![]!);
+ method public void getPos(double, double[]!);
+ method public void getPos(double, float[]!);
+ method public double getPos(double, int);
+ method public void getVelocity(double, double[]!);
+ method public void setup(double[]![]!);
+ }
+
+ public static class HyperSpline.Cubic {
+ ctor public HyperSpline.Cubic(double, double, double, double);
+ method public double eval(double);
+ method public double vel(double);
+ }
+
+ public class KeyCache {
+ ctor public KeyCache();
+ method public float getFloatValue(Object!, String!, int);
+ method public void setFloatValue(Object!, String!, int, float);
+ }
+
+ public abstract class KeyCycleOscillator {
+ ctor public KeyCycleOscillator();
+ method public float get(float);
+ method public androidx.constraintlayout.core.motion.utils.CurveFit! getCurveFit();
+ method public float getSlope(float);
+ method public static androidx.constraintlayout.core.motion.utils.KeyCycleOscillator! makeWidgetCycle(String!);
+ method protected void setCustom(Object!);
+ method public void setPoint(int, int, String!, int, float, float, float, float);
+ method public void setPoint(int, int, String!, int, float, float, float, float, Object!);
+ method public void setProperty(androidx.constraintlayout.core.motion.MotionWidget!, float);
+ method public void setType(String!);
+ method public void setup(float);
+ method public boolean variesByPath();
+ field public int mVariesBy;
+ }
+
+ public static class KeyCycleOscillator.PathRotateSet extends androidx.constraintlayout.core.motion.utils.KeyCycleOscillator {
+ ctor public KeyCycleOscillator.PathRotateSet(String!);
+ method public void setPathRotate(androidx.constraintlayout.core.motion.MotionWidget!, float, double, double);
+ }
+
+ public class KeyFrameArray {
+ ctor public KeyFrameArray();
+ }
+
+ public static class KeyFrameArray.CustomArray {
+ ctor public KeyFrameArray.CustomArray();
+ method public void append(int, androidx.constraintlayout.core.motion.CustomAttribute!);
+ method public void clear();
+ method public void dump();
+ method public int keyAt(int);
+ method public void remove(int);
+ method public int size();
+ method public androidx.constraintlayout.core.motion.CustomAttribute! valueAt(int);
+ }
+
+ public static class KeyFrameArray.CustomVar {
+ ctor public KeyFrameArray.CustomVar();
+ method public void append(int, androidx.constraintlayout.core.motion.CustomVariable!);
+ method public void clear();
+ method public void dump();
+ method public int keyAt(int);
+ method public void remove(int);
+ method public int size();
+ method public androidx.constraintlayout.core.motion.CustomVariable! valueAt(int);
+ }
+
+ public class LinearCurveFit extends androidx.constraintlayout.core.motion.utils.CurveFit {
+ ctor public LinearCurveFit(double[]!, double[]![]!);
+ method public void getPos(double, double[]!);
+ method public void getPos(double, float[]!);
+ method public double getPos(double, int);
+ method public void getSlope(double, double[]!);
+ method public double getSlope(double, int);
+ method public double[]! getTimePoints();
+ }
+
+ public class MonotonicCurveFit extends androidx.constraintlayout.core.motion.utils.CurveFit {
+ ctor public MonotonicCurveFit(double[]!, double[]![]!);
+ method public static androidx.constraintlayout.core.motion.utils.MonotonicCurveFit! buildWave(String!);
+ method public void getPos(double, double[]!);
+ method public void getPos(double, float[]!);
+ method public double getPos(double, int);
+ method public void getSlope(double, double[]!);
+ method public double getSlope(double, int);
+ method public double[]! getTimePoints();
+ }
+
+ public class Oscillator {
+ ctor public Oscillator();
+ method public void addPoint(double, float);
+ method public double getSlope(double, double, double);
+ method public double getValue(double, double);
+ method public void normalize();
+ method public void setType(int, String!);
+ field public static final int BOUNCE = 6; // 0x6
+ field public static final int COS_WAVE = 5; // 0x5
+ field public static final int CUSTOM = 7; // 0x7
+ field public static final int REVERSE_SAW_WAVE = 4; // 0x4
+ field public static final int SAW_WAVE = 3; // 0x3
+ field public static final int SIN_WAVE = 0; // 0x0
+ field public static final int SQUARE_WAVE = 1; // 0x1
+ field public static String! TAG;
+ field public static final int TRIANGLE_WAVE = 2; // 0x2
+ }
+
+ public class Rect {
+ ctor public Rect();
+ method public int height();
+ method public int width();
+ field public int bottom;
+ field public int left;
+ field public int right;
+ field public int top;
+ }
+
+ public class Schlick extends androidx.constraintlayout.core.motion.utils.Easing {
+ }
+
+ public abstract class SplineSet {
+ ctor public SplineSet();
+ method public float get(float);
+ method public androidx.constraintlayout.core.motion.utils.CurveFit! getCurveFit();
+ method public float getSlope(float);
+ method public static androidx.constraintlayout.core.motion.utils.SplineSet! makeCustomSpline(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomArray!);
+ method public static androidx.constraintlayout.core.motion.utils.SplineSet! makeCustomSplineSet(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomVar!);
+ method public static androidx.constraintlayout.core.motion.utils.SplineSet! makeSpline(String!, long);
+ method public void setPoint(int, float);
+ method public void setProperty(androidx.constraintlayout.core.motion.utils.TypedValues!, float);
+ method public void setType(String!);
+ method public void setup(int);
+ field protected androidx.constraintlayout.core.motion.utils.CurveFit! mCurveFit;
+ field protected int[]! mTimePoints;
+ field protected float[]! mValues;
+ }
+
+ public static class SplineSet.CustomSet extends androidx.constraintlayout.core.motion.utils.SplineSet {
+ ctor public SplineSet.CustomSet(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomArray!);
+ method public void setPoint(int, androidx.constraintlayout.core.motion.CustomAttribute!);
+ method public void setProperty(androidx.constraintlayout.core.state.WidgetFrame!, float);
+ }
+
+ public static class SplineSet.CustomSpline extends androidx.constraintlayout.core.motion.utils.SplineSet {
+ ctor public SplineSet.CustomSpline(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomVar!);
+ method public void setPoint(int, androidx.constraintlayout.core.motion.CustomVariable!);
+ method public void setProperty(androidx.constraintlayout.core.motion.MotionWidget!, float);
+ }
+
+ public class SpringStopEngine implements androidx.constraintlayout.core.motion.utils.StopEngine {
+ ctor public SpringStopEngine();
+ method public String! debug(String!, float);
+ method public float getAcceleration();
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ method public float getVelocity(float);
+ method public boolean isStopped();
+ method public void springConfig(float, float, float, float, float, float, float, int);
+ }
+
+ public class StepCurve extends androidx.constraintlayout.core.motion.utils.Easing {
+ }
+
+ public interface StopEngine {
+ method public String! debug(String!, float);
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ method public float getVelocity(float);
+ method public boolean isStopped();
+ }
+
+ public class StopLogicEngine implements androidx.constraintlayout.core.motion.utils.StopEngine {
+ ctor public StopLogicEngine();
+ method public void config(float, float, float, float, float, float);
+ method public String! debug(String!, float);
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ method public float getVelocity(float);
+ method public boolean isStopped();
+ }
+
+ public static class StopLogicEngine.Decelerate implements androidx.constraintlayout.core.motion.utils.StopEngine {
+ ctor public StopLogicEngine.Decelerate();
+ method public void config(float, float, float);
+ method public String! debug(String!, float);
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ method public float getVelocity(float);
+ method public boolean isStopped();
+ }
+
+ public abstract class TimeCycleSplineSet {
+ ctor public TimeCycleSplineSet();
+ method protected float calcWave(float);
+ method public androidx.constraintlayout.core.motion.utils.CurveFit! getCurveFit();
+ method public void setPoint(int, float, float, int, float);
+ method protected void setStartTime(long);
+ method public void setType(String!);
+ method public void setup(int);
+ field protected static final int CURVE_OFFSET = 2; // 0x2
+ field protected static final int CURVE_PERIOD = 1; // 0x1
+ field protected static final int CURVE_VALUE = 0; // 0x0
+ field protected float[]! mCache;
+ field protected boolean mContinue;
+ field protected int mCount;
+ field protected androidx.constraintlayout.core.motion.utils.CurveFit! mCurveFit;
+ field protected float mLastCycle;
+ field protected long mLastTime;
+ field protected int[]! mTimePoints;
+ field protected String! mType;
+ field protected float[]![]! mValues;
+ field protected int mWaveShape;
+ field protected static float sVal2PI;
+ }
+
+ public static class TimeCycleSplineSet.CustomSet extends androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet {
+ ctor public TimeCycleSplineSet.CustomSet(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomArray!);
+ method public void setPoint(int, androidx.constraintlayout.core.motion.CustomAttribute!, float, int, float);
+ method public boolean setProperty(androidx.constraintlayout.core.motion.MotionWidget!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ }
+
+ public static class TimeCycleSplineSet.CustomVarSet extends androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet {
+ ctor public TimeCycleSplineSet.CustomVarSet(String!, androidx.constraintlayout.core.motion.utils.KeyFrameArray.CustomVar!);
+ method public void setPoint(int, androidx.constraintlayout.core.motion.CustomVariable!, float, int, float);
+ method public boolean setProperty(androidx.constraintlayout.core.motion.MotionWidget!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ }
+
+ protected static class TimeCycleSplineSet.Sort {
+ ctor protected TimeCycleSplineSet.Sort();
+ }
+
+ public class TypedBundle {
+ ctor public TypedBundle();
+ method public void add(int, boolean);
+ method public void add(int, float);
+ method public void add(int, int);
+ method public void add(int, String!);
+ method public void addIfNotNull(int, String!);
+ method public void applyDelta(androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void applyDelta(androidx.constraintlayout.core.motion.utils.TypedValues!);
+ method public void clear();
+ method public int getInteger(int);
+ }
+
+ public interface TypedValues {
+ method public int getId(String!);
+ method public boolean setValue(int, boolean);
+ method public boolean setValue(int, float);
+ method public boolean setValue(int, int);
+ method public boolean setValue(int, String!);
+ field public static final int BOOLEAN_MASK = 1; // 0x1
+ field public static final int FLOAT_MASK = 4; // 0x4
+ field public static final int INT_MASK = 2; // 0x2
+ field public static final int STRING_MASK = 8; // 0x8
+ field public static final String S_CUSTOM = "CUSTOM";
+ field public static final int TYPE_FRAME_POSITION = 100; // 0x64
+ field public static final int TYPE_TARGET = 101; // 0x65
+ }
+
+ public static interface TypedValues.AttributesType {
+ method public static int getId(String!);
+ method public static int getType(int);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "KeyAttributes";
+ field public static final String S_ALPHA = "alpha";
+ field public static final String S_CURVE_FIT = "curveFit";
+ field public static final String S_CUSTOM = "CUSTOM";
+ field public static final String S_EASING = "easing";
+ field public static final String S_ELEVATION = "elevation";
+ field public static final String S_FRAME = "frame";
+ field public static final String S_PATH_ROTATE = "pathRotate";
+ field public static final String S_PIVOT_TARGET = "pivotTarget";
+ field public static final String S_PIVOT_X = "pivotX";
+ field public static final String S_PIVOT_Y = "pivotY";
+ field public static final String S_PROGRESS = "progress";
+ field public static final String S_ROTATION_X = "rotationX";
+ field public static final String S_ROTATION_Y = "rotationY";
+ field public static final String S_ROTATION_Z = "rotationZ";
+ field public static final String S_SCALE_X = "scaleX";
+ field public static final String S_SCALE_Y = "scaleY";
+ field public static final String S_TARGET = "target";
+ field public static final String S_TRANSLATION_X = "translationX";
+ field public static final String S_TRANSLATION_Y = "translationY";
+ field public static final String S_TRANSLATION_Z = "translationZ";
+ field public static final String S_VISIBILITY = "visibility";
+ field public static final int TYPE_ALPHA = 303; // 0x12f
+ field public static final int TYPE_CURVE_FIT = 301; // 0x12d
+ field public static final int TYPE_EASING = 317; // 0x13d
+ field public static final int TYPE_ELEVATION = 307; // 0x133
+ field public static final int TYPE_PATH_ROTATE = 316; // 0x13c
+ field public static final int TYPE_PIVOT_TARGET = 318; // 0x13e
+ field public static final int TYPE_PIVOT_X = 313; // 0x139
+ field public static final int TYPE_PIVOT_Y = 314; // 0x13a
+ field public static final int TYPE_PROGRESS = 315; // 0x13b
+ field public static final int TYPE_ROTATION_X = 308; // 0x134
+ field public static final int TYPE_ROTATION_Y = 309; // 0x135
+ field public static final int TYPE_ROTATION_Z = 310; // 0x136
+ field public static final int TYPE_SCALE_X = 311; // 0x137
+ field public static final int TYPE_SCALE_Y = 312; // 0x138
+ field public static final int TYPE_TRANSLATION_X = 304; // 0x130
+ field public static final int TYPE_TRANSLATION_Y = 305; // 0x131
+ field public static final int TYPE_TRANSLATION_Z = 306; // 0x132
+ field public static final int TYPE_VISIBILITY = 302; // 0x12e
+ }
+
+ public static interface TypedValues.Custom {
+ method public static int getId(String!);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "Custom";
+ field public static final String S_BOOLEAN = "boolean";
+ field public static final String S_COLOR = "color";
+ field public static final String S_DIMENSION = "dimension";
+ field public static final String S_FLOAT = "float";
+ field public static final String S_INT = "integer";
+ field public static final String S_REFERENCE = "reference";
+ field public static final String S_STRING = "string";
+ field public static final int TYPE_BOOLEAN = 904; // 0x388
+ field public static final int TYPE_COLOR = 902; // 0x386
+ field public static final int TYPE_DIMENSION = 905; // 0x389
+ field public static final int TYPE_FLOAT = 901; // 0x385
+ field public static final int TYPE_INT = 900; // 0x384
+ field public static final int TYPE_REFERENCE = 906; // 0x38a
+ field public static final int TYPE_STRING = 903; // 0x387
+ }
+
+ public static interface TypedValues.CycleType {
+ method public static int getId(String!);
+ method public static int getType(int);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "KeyCycle";
+ field public static final String S_ALPHA = "alpha";
+ field public static final String S_CURVE_FIT = "curveFit";
+ field public static final String S_CUSTOM_WAVE_SHAPE = "customWave";
+ field public static final String S_EASING = "easing";
+ field public static final String S_ELEVATION = "elevation";
+ field public static final String S_PATH_ROTATE = "pathRotate";
+ field public static final String S_PIVOT_X = "pivotX";
+ field public static final String S_PIVOT_Y = "pivotY";
+ field public static final String S_PROGRESS = "progress";
+ field public static final String S_ROTATION_X = "rotationX";
+ field public static final String S_ROTATION_Y = "rotationY";
+ field public static final String S_ROTATION_Z = "rotationZ";
+ field public static final String S_SCALE_X = "scaleX";
+ field public static final String S_SCALE_Y = "scaleY";
+ field public static final String S_TRANSLATION_X = "translationX";
+ field public static final String S_TRANSLATION_Y = "translationY";
+ field public static final String S_TRANSLATION_Z = "translationZ";
+ field public static final String S_VISIBILITY = "visibility";
+ field public static final String S_WAVE_OFFSET = "offset";
+ field public static final String S_WAVE_PERIOD = "period";
+ field public static final String S_WAVE_PHASE = "phase";
+ field public static final String S_WAVE_SHAPE = "waveShape";
+ field public static final int TYPE_ALPHA = 403; // 0x193
+ field public static final int TYPE_CURVE_FIT = 401; // 0x191
+ field public static final int TYPE_CUSTOM_WAVE_SHAPE = 422; // 0x1a6
+ field public static final int TYPE_EASING = 420; // 0x1a4
+ field public static final int TYPE_ELEVATION = 307; // 0x133
+ field public static final int TYPE_PATH_ROTATE = 416; // 0x1a0
+ field public static final int TYPE_PIVOT_X = 313; // 0x139
+ field public static final int TYPE_PIVOT_Y = 314; // 0x13a
+ field public static final int TYPE_PROGRESS = 315; // 0x13b
+ field public static final int TYPE_ROTATION_X = 308; // 0x134
+ field public static final int TYPE_ROTATION_Y = 309; // 0x135
+ field public static final int TYPE_ROTATION_Z = 310; // 0x136
+ field public static final int TYPE_SCALE_X = 311; // 0x137
+ field public static final int TYPE_SCALE_Y = 312; // 0x138
+ field public static final int TYPE_TRANSLATION_X = 304; // 0x130
+ field public static final int TYPE_TRANSLATION_Y = 305; // 0x131
+ field public static final int TYPE_TRANSLATION_Z = 306; // 0x132
+ field public static final int TYPE_VISIBILITY = 402; // 0x192
+ field public static final int TYPE_WAVE_OFFSET = 424; // 0x1a8
+ field public static final int TYPE_WAVE_PERIOD = 423; // 0x1a7
+ field public static final int TYPE_WAVE_PHASE = 425; // 0x1a9
+ field public static final int TYPE_WAVE_SHAPE = 421; // 0x1a5
+ }
+
+ public static interface TypedValues.MotionScene {
+ method public static int getId(String!);
+ method public static int getType(int);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "MotionScene";
+ field public static final String S_DEFAULT_DURATION = "defaultDuration";
+ field public static final String S_LAYOUT_DURING_TRANSITION = "layoutDuringTransition";
+ field public static final int TYPE_DEFAULT_DURATION = 600; // 0x258
+ field public static final int TYPE_LAYOUT_DURING_TRANSITION = 601; // 0x259
+ }
+
+ public static interface TypedValues.MotionType {
+ method public static int getId(String!);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "Motion";
+ field public static final String S_ANIMATE_CIRCLEANGLE_TO = "AnimateCircleAngleTo";
+ field public static final String S_ANIMATE_RELATIVE_TO = "AnimateRelativeTo";
+ field public static final String S_DRAW_PATH = "DrawPath";
+ field public static final String S_EASING = "TransitionEasing";
+ field public static final String S_PATHMOTION_ARC = "PathMotionArc";
+ field public static final String S_PATH_ROTATE = "PathRotate";
+ field public static final String S_POLAR_RELATIVETO = "PolarRelativeTo";
+ field public static final String S_QUANTIZE_INTERPOLATOR = "QuantizeInterpolator";
+ field public static final String S_QUANTIZE_INTERPOLATOR_ID = "QuantizeInterpolatorID";
+ field public static final String S_QUANTIZE_INTERPOLATOR_TYPE = "QuantizeInterpolatorType";
+ field public static final String S_QUANTIZE_MOTIONSTEPS = "QuantizeMotionSteps";
+ field public static final String S_QUANTIZE_MOTION_PHASE = "QuantizeMotionPhase";
+ field public static final String S_STAGGER = "Stagger";
+ field public static final int TYPE_ANIMATE_CIRCLEANGLE_TO = 606; // 0x25e
+ field public static final int TYPE_ANIMATE_RELATIVE_TO = 605; // 0x25d
+ field public static final int TYPE_DRAW_PATH = 608; // 0x260
+ field public static final int TYPE_EASING = 603; // 0x25b
+ field public static final int TYPE_PATHMOTION_ARC = 607; // 0x25f
+ field public static final int TYPE_PATH_ROTATE = 601; // 0x259
+ field public static final int TYPE_POLAR_RELATIVETO = 609; // 0x261
+ field public static final int TYPE_QUANTIZE_INTERPOLATOR = 604; // 0x25c
+ field public static final int TYPE_QUANTIZE_INTERPOLATOR_ID = 612; // 0x264
+ field public static final int TYPE_QUANTIZE_INTERPOLATOR_TYPE = 611; // 0x263
+ field public static final int TYPE_QUANTIZE_MOTIONSTEPS = 610; // 0x262
+ field public static final int TYPE_QUANTIZE_MOTION_PHASE = 602; // 0x25a
+ field public static final int TYPE_STAGGER = 600; // 0x258
+ }
+
+ public static interface TypedValues.OnSwipe {
+ field public static final String AUTOCOMPLETE_MODE = "autocompletemode";
+ field public static final String![]! AUTOCOMPLETE_MODE_ENUM;
+ field public static final String DRAG_DIRECTION = "dragdirection";
+ field public static final String DRAG_SCALE = "dragscale";
+ field public static final String DRAG_THRESHOLD = "dragthreshold";
+ field public static final String LIMIT_BOUNDS_TO = "limitboundsto";
+ field public static final String MAX_ACCELERATION = "maxacceleration";
+ field public static final String MAX_VELOCITY = "maxvelocity";
+ field public static final String MOVE_WHEN_SCROLLAT_TOP = "movewhenscrollattop";
+ field public static final String NESTED_SCROLL_FLAGS = "nestedscrollflags";
+ field public static final String![]! NESTED_SCROLL_FLAGS_ENUM;
+ field public static final String ON_TOUCH_UP = "ontouchup";
+ field public static final String![]! ON_TOUCH_UP_ENUM;
+ field public static final String ROTATION_CENTER_ID = "rotationcenterid";
+ field public static final String SPRINGS_TOP_THRESHOLD = "springstopthreshold";
+ field public static final String SPRING_BOUNDARY = "springboundary";
+ field public static final String![]! SPRING_BOUNDARY_ENUM;
+ field public static final String SPRING_DAMPING = "springdamping";
+ field public static final String SPRING_MASS = "springmass";
+ field public static final String SPRING_STIFFNESS = "springstiffness";
+ field public static final String TOUCH_ANCHOR_ID = "touchanchorid";
+ field public static final String TOUCH_ANCHOR_SIDE = "touchanchorside";
+ field public static final String TOUCH_REGION_ID = "touchregionid";
+ }
+
+ public static interface TypedValues.PositionType {
+ method public static int getId(String!);
+ method public static int getType(int);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "KeyPosition";
+ field public static final String S_DRAWPATH = "drawPath";
+ field public static final String S_PERCENT_HEIGHT = "percentHeight";
+ field public static final String S_PERCENT_WIDTH = "percentWidth";
+ field public static final String S_PERCENT_X = "percentX";
+ field public static final String S_PERCENT_Y = "percentY";
+ field public static final String S_SIZE_PERCENT = "sizePercent";
+ field public static final String S_TRANSITION_EASING = "transitionEasing";
+ field public static final int TYPE_CURVE_FIT = 508; // 0x1fc
+ field public static final int TYPE_DRAWPATH = 502; // 0x1f6
+ field public static final int TYPE_PATH_MOTION_ARC = 509; // 0x1fd
+ field public static final int TYPE_PERCENT_HEIGHT = 504; // 0x1f8
+ field public static final int TYPE_PERCENT_WIDTH = 503; // 0x1f7
+ field public static final int TYPE_PERCENT_X = 506; // 0x1fa
+ field public static final int TYPE_PERCENT_Y = 507; // 0x1fb
+ field public static final int TYPE_POSITION_TYPE = 510; // 0x1fe
+ field public static final int TYPE_SIZE_PERCENT = 505; // 0x1f9
+ field public static final int TYPE_TRANSITION_EASING = 501; // 0x1f5
+ }
+
+ public static interface TypedValues.TransitionType {
+ method public static int getId(String!);
+ method public static int getType(int);
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "Transitions";
+ field public static final String S_AUTO_TRANSITION = "autoTransition";
+ field public static final String S_DURATION = "duration";
+ field public static final String S_FROM = "from";
+ field public static final String S_INTERPOLATOR = "motionInterpolator";
+ field public static final String S_PATH_MOTION_ARC = "pathMotionArc";
+ field public static final String S_STAGGERED = "staggered";
+ field public static final String S_TO = "to";
+ field public static final String S_TRANSITION_FLAGS = "transitionFlags";
+ field public static final int TYPE_AUTO_TRANSITION = 704; // 0x2c0
+ field public static final int TYPE_DURATION = 700; // 0x2bc
+ field public static final int TYPE_FROM = 701; // 0x2bd
+ field public static final int TYPE_INTERPOLATOR = 705; // 0x2c1
+ field public static final int TYPE_PATH_MOTION_ARC = 509; // 0x1fd
+ field public static final int TYPE_STAGGERED = 706; // 0x2c2
+ field public static final int TYPE_TO = 702; // 0x2be
+ field public static final int TYPE_TRANSITION_FLAGS = 707; // 0x2c3
+ }
+
+ public static interface TypedValues.TriggerType {
+ method public static int getId(String!);
+ field public static final String CROSS = "CROSS";
+ field public static final String![]! KEY_WORDS;
+ field public static final String NAME = "KeyTrigger";
+ field public static final String NEGATIVE_CROSS = "negativeCross";
+ field public static final String POSITIVE_CROSS = "positiveCross";
+ field public static final String POST_LAYOUT = "postLayout";
+ field public static final String TRIGGER_COLLISION_ID = "triggerCollisionId";
+ field public static final String TRIGGER_COLLISION_VIEW = "triggerCollisionView";
+ field public static final String TRIGGER_ID = "triggerID";
+ field public static final String TRIGGER_RECEIVER = "triggerReceiver";
+ field public static final String TRIGGER_SLACK = "triggerSlack";
+ field public static final int TYPE_CROSS = 312; // 0x138
+ field public static final int TYPE_NEGATIVE_CROSS = 310; // 0x136
+ field public static final int TYPE_POSITIVE_CROSS = 309; // 0x135
+ field public static final int TYPE_POST_LAYOUT = 304; // 0x130
+ field public static final int TYPE_TRIGGER_COLLISION_ID = 307; // 0x133
+ field public static final int TYPE_TRIGGER_COLLISION_VIEW = 306; // 0x132
+ field public static final int TYPE_TRIGGER_ID = 308; // 0x134
+ field public static final int TYPE_TRIGGER_RECEIVER = 311; // 0x137
+ field public static final int TYPE_TRIGGER_SLACK = 305; // 0x131
+ field public static final int TYPE_VIEW_TRANSITION_ON_CROSS = 301; // 0x12d
+ field public static final int TYPE_VIEW_TRANSITION_ON_NEGATIVE_CROSS = 303; // 0x12f
+ field public static final int TYPE_VIEW_TRANSITION_ON_POSITIVE_CROSS = 302; // 0x12e
+ field public static final String VIEW_TRANSITION_ON_CROSS = "viewTransitionOnCross";
+ field public static final String VIEW_TRANSITION_ON_NEGATIVE_CROSS = "viewTransitionOnNegativeCross";
+ field public static final String VIEW_TRANSITION_ON_POSITIVE_CROSS = "viewTransitionOnPositiveCross";
+ }
+
+ public class Utils {
+ ctor public Utils();
+ method public int getInterpolatedColor(float[]!);
+ method public static void log(String!);
+ method public static void log(String!, String!);
+ method public static void logStack(String!, int);
+ method public static void loge(String!, String!);
+ method public static int rgbaTocColor(float, float, float, float);
+ method public static void setDebugHandle(androidx.constraintlayout.core.motion.utils.Utils.DebugHandle!);
+ method public static void socketSend(String!);
+ }
+
+ public static interface Utils.DebugHandle {
+ method public void message(String!);
+ }
+
+ public class VelocityMatrix {
+ ctor public VelocityMatrix();
+ method public void applyTransform(float, float, int, int, float[]!);
+ method public void clear();
+ method public void setRotationVelocity(androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!, float);
+ method public void setRotationVelocity(androidx.constraintlayout.core.motion.utils.SplineSet!, float);
+ method public void setScaleVelocity(androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!, androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!, float);
+ method public void setScaleVelocity(androidx.constraintlayout.core.motion.utils.SplineSet!, androidx.constraintlayout.core.motion.utils.SplineSet!, float);
+ method public void setTranslationVelocity(androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!, androidx.constraintlayout.core.motion.utils.KeyCycleOscillator!, float);
+ method public void setTranslationVelocity(androidx.constraintlayout.core.motion.utils.SplineSet!, androidx.constraintlayout.core.motion.utils.SplineSet!, float);
+ }
+
+ public class ViewState {
+ ctor public ViewState();
+ method public void getState(androidx.constraintlayout.core.motion.MotionWidget!);
+ method public int height();
+ method public int width();
+ field public int bottom;
+ field public int left;
+ field public int right;
+ field public float rotation;
+ field public int top;
+ }
+
+}
+
+package androidx.constraintlayout.core.parser {
+
+ public class CLArray extends androidx.constraintlayout.core.parser.CLContainer {
+ ctor public CLArray(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ }
+
+ public class CLContainer extends androidx.constraintlayout.core.parser.CLElement {
+ ctor public CLContainer(char[]!);
+ method public void add(androidx.constraintlayout.core.parser.CLElement!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ method public void clear();
+ method public androidx.constraintlayout.core.parser.CLContainer clone();
+ method public androidx.constraintlayout.core.parser.CLElement! get(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLElement! get(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLArray! getArray(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLArray! getArray(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLArray! getArrayOrCreate(String!);
+ method public androidx.constraintlayout.core.parser.CLArray! getArrayOrNull(String!);
+ method public boolean getBoolean(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public boolean getBoolean(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public float getFloat(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public float getFloat(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public float getFloatOrNaN(String!);
+ method public int getInt(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public int getInt(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLObject! getObject(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLObject! getObject(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLObject! getObjectOrNull(String!);
+ method public androidx.constraintlayout.core.parser.CLElement! getOrNull(int);
+ method public androidx.constraintlayout.core.parser.CLElement! getOrNull(String!);
+ method public String! getString(int) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public String! getString(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public String! getStringOrNull(int);
+ method public String! getStringOrNull(String!);
+ method public boolean has(String!);
+ method public java.util.ArrayList<java.lang.String!>! names();
+ method public void put(String!, androidx.constraintlayout.core.parser.CLElement!);
+ method public void putNumber(String!, float);
+ method public void putString(String!, String!);
+ method public void remove(String!);
+ method public int size();
+ }
+
+ public class CLElement implements java.lang.Cloneable {
+ ctor public CLElement(char[]!);
+ method protected void addIndent(StringBuilder!, int);
+ method public androidx.constraintlayout.core.parser.CLElement clone();
+ method public String! content();
+ method public androidx.constraintlayout.core.parser.CLElement! getContainer();
+ method protected String! getDebugName();
+ method public long getEnd();
+ method public float getFloat();
+ method public int getInt();
+ method public int getLine();
+ method public long getStart();
+ method protected String! getStrClass();
+ method public boolean hasContent();
+ method public boolean isDone();
+ method public boolean isStarted();
+ method public boolean notStarted();
+ method public void setContainer(androidx.constraintlayout.core.parser.CLContainer!);
+ method public void setEnd(long);
+ method public void setLine(int);
+ method public void setStart(long);
+ method protected String! toFormattedJSON(int, int);
+ method protected String! toJSON();
+ field protected androidx.constraintlayout.core.parser.CLContainer! mContainer;
+ field protected long mEnd;
+ field protected long mStart;
+ field protected static int sBaseIndent;
+ field protected static int sMaxLine;
+ }
+
+ public class CLKey extends androidx.constraintlayout.core.parser.CLContainer {
+ ctor public CLKey(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(String!, androidx.constraintlayout.core.parser.CLElement!);
+ method public String! getName();
+ method public androidx.constraintlayout.core.parser.CLElement! getValue();
+ method public void set(androidx.constraintlayout.core.parser.CLElement!);
+ }
+
+ public class CLNumber extends androidx.constraintlayout.core.parser.CLElement {
+ ctor public CLNumber(char[]!);
+ ctor public CLNumber(float);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ method public boolean isInt();
+ method public void putValue(float);
+ }
+
+ public class CLObject extends androidx.constraintlayout.core.parser.CLContainer implements java.lang.Iterable<androidx.constraintlayout.core.parser.CLKey!> {
+ ctor public CLObject(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLObject! allocate(char[]!);
+ method public androidx.constraintlayout.core.parser.CLObject clone();
+ method public java.util.Iterator<androidx.constraintlayout.core.parser.CLKey!>! iterator();
+ method public String! toFormattedJSON();
+ method public String! toFormattedJSON(int, int);
+ method public String! toJSON();
+ }
+
+ public class CLParser {
+ ctor public CLParser(String!);
+ method public androidx.constraintlayout.core.parser.CLObject! parse() throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public static androidx.constraintlayout.core.parser.CLObject! parse(String!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ }
+
+ public class CLParsingException extends java.lang.Exception {
+ ctor public CLParsingException(String!, androidx.constraintlayout.core.parser.CLElement!);
+ method public String! reason();
+ }
+
+ public class CLString extends androidx.constraintlayout.core.parser.CLElement {
+ ctor public CLString(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLString from(String);
+ }
+
+ public class CLToken extends androidx.constraintlayout.core.parser.CLElement {
+ ctor public CLToken(char[]!);
+ method public static androidx.constraintlayout.core.parser.CLElement! allocate(char[]!);
+ method public boolean getBoolean() throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.parser.CLToken.Type! getType();
+ method public boolean isNull() throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public boolean validate(char, long);
+ }
+
+}
+
+package androidx.constraintlayout.core.state {
+
+ public class ConstraintReference implements androidx.constraintlayout.core.state.Reference {
+ ctor public ConstraintReference(androidx.constraintlayout.core.state.State!);
+ method public void addCustomColor(String!, int);
+ method public void addCustomFloat(String!, float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! alpha(float);
+ method public void apply();
+ method public void applyWidgetConstraints();
+ method public androidx.constraintlayout.core.state.ConstraintReference! baseline();
+ method public androidx.constraintlayout.core.state.ConstraintReference! baselineToBaseline(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! baselineToBottom(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! baselineToTop(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! bias(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! bottom();
+ method public androidx.constraintlayout.core.state.ConstraintReference! bottomToBottom(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! bottomToTop(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! centerHorizontally(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! centerVertically(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! circularConstraint(Object!, float, float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! clear();
+ method public androidx.constraintlayout.core.state.ConstraintReference! clearAll();
+ method public androidx.constraintlayout.core.state.ConstraintReference! clearHorizontal();
+ method public androidx.constraintlayout.core.state.ConstraintReference! clearVertical();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! createConstraintWidget();
+ method public androidx.constraintlayout.core.state.ConstraintReference! end();
+ method public androidx.constraintlayout.core.state.ConstraintReference! endToEnd(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! endToStart(Object!);
+ method public float getAlpha();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getConstraintWidget();
+ method public androidx.constraintlayout.core.state.helpers.Facade! getFacade();
+ method public androidx.constraintlayout.core.state.Dimension! getHeight();
+ method public int getHorizontalChainStyle();
+ method public float getHorizontalChainWeight();
+ method public Object! getKey();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotationX();
+ method public float getRotationY();
+ method public float getRotationZ();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public String! getTag();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public float getTranslationZ();
+ method public int getVerticalChainStyle(int);
+ method public float getVerticalChainWeight();
+ method public Object! getView();
+ method public androidx.constraintlayout.core.state.Dimension! getWidth();
+ method public androidx.constraintlayout.core.state.ConstraintReference! height(androidx.constraintlayout.core.state.Dimension!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! horizontalBias(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! left();
+ method public androidx.constraintlayout.core.state.ConstraintReference! leftToLeft(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! leftToRight(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! margin(int);
+ method public androidx.constraintlayout.core.state.ConstraintReference! margin(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! marginGone(int);
+ method public androidx.constraintlayout.core.state.ConstraintReference! marginGone(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! pivotX(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! pivotY(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! right();
+ method public androidx.constraintlayout.core.state.ConstraintReference! rightToLeft(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! rightToRight(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! rotationX(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! rotationY(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! rotationZ(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! scaleX(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! scaleY(float);
+ method public void setConstraintWidget(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void setFacade(androidx.constraintlayout.core.state.helpers.Facade!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! setHeight(androidx.constraintlayout.core.state.Dimension!);
+ method public void setHorizontalChainStyle(int);
+ method public void setHorizontalChainWeight(float);
+ method public void setKey(Object!);
+ method public void setTag(String!);
+ method public void setVerticalChainStyle(int);
+ method public void setVerticalChainWeight(float);
+ method public void setView(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! setWidth(androidx.constraintlayout.core.state.Dimension!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! start();
+ method public androidx.constraintlayout.core.state.ConstraintReference! startToEnd(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! startToStart(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! top();
+ method public androidx.constraintlayout.core.state.ConstraintReference! topToBottom(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! topToTop(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! translationX(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! translationY(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! translationZ(float);
+ method public void validate() throws java.lang.Exception;
+ method public androidx.constraintlayout.core.state.ConstraintReference! verticalBias(float);
+ method public androidx.constraintlayout.core.state.ConstraintReference! visibility(int);
+ method public androidx.constraintlayout.core.state.ConstraintReference! width(androidx.constraintlayout.core.state.Dimension!);
+ field protected Object! mBottomToBottom;
+ field protected Object! mBottomToTop;
+ field protected Object! mEndToEnd;
+ field protected Object! mEndToStart;
+ field protected float mHorizontalBias;
+ field protected Object! mLeftToLeft;
+ field protected Object! mLeftToRight;
+ field protected int mMarginBottom;
+ field protected int mMarginBottomGone;
+ field protected int mMarginEnd;
+ field protected int mMarginEndGone;
+ field protected int mMarginLeft;
+ field protected int mMarginLeftGone;
+ field protected int mMarginRight;
+ field protected int mMarginRightGone;
+ field protected int mMarginStart;
+ field protected int mMarginStartGone;
+ field protected int mMarginTop;
+ field protected int mMarginTopGone;
+ field protected Object! mRightToLeft;
+ field protected Object! mRightToRight;
+ field protected Object! mStartToEnd;
+ field protected Object! mStartToStart;
+ field protected Object! mTopToBottom;
+ field protected Object! mTopToTop;
+ field protected float mVerticalBias;
+ }
+
+ public static interface ConstraintReference.ConstraintReferenceFactory {
+ method public androidx.constraintlayout.core.state.ConstraintReference! create(androidx.constraintlayout.core.state.State!);
+ }
+
+ public class ConstraintSetParser {
+ ctor public ConstraintSetParser();
+ method public static void parseDesignElementsJSON(String!, java.util.ArrayList<androidx.constraintlayout.core.state.ConstraintSetParser.DesignElement!>!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public static void parseJSON(String!, androidx.constraintlayout.core.state.State!, androidx.constraintlayout.core.state.ConstraintSetParser.LayoutVariables!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public static void parseJSON(String!, androidx.constraintlayout.core.state.Transition!, int);
+ method public static void parseMotionSceneJSON(androidx.constraintlayout.core.state.CoreMotionScene!, String!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static void populateState(androidx.constraintlayout.core.parser.CLObject, androidx.constraintlayout.core.state.State, androidx.constraintlayout.core.state.ConstraintSetParser.LayoutVariables) throws androidx.constraintlayout.core.parser.CLParsingException;
+ }
+
+ public static class ConstraintSetParser.DesignElement {
+ method public String! getId();
+ method public java.util.HashMap<java.lang.String!,java.lang.String!>! getParams();
+ method public String! getType();
+ }
+
+ public static class ConstraintSetParser.LayoutVariables {
+ ctor public ConstraintSetParser.LayoutVariables();
+ method public void putOverride(String!, float);
+ }
+
+ public enum ConstraintSetParser.MotionLayoutDebugFlags {
+ enum_constant public static final androidx.constraintlayout.core.state.ConstraintSetParser.MotionLayoutDebugFlags NONE;
+ enum_constant public static final androidx.constraintlayout.core.state.ConstraintSetParser.MotionLayoutDebugFlags SHOW_ALL;
+ enum_constant public static final androidx.constraintlayout.core.state.ConstraintSetParser.MotionLayoutDebugFlags UNKNOWN;
+ }
+
+ public interface CoreMotionScene {
+ method public String! getConstraintSet(int);
+ method public String! getConstraintSet(String!);
+ method public String! getTransition(String!);
+ method public void setConstraintSetContent(String!, String!);
+ method public void setDebugName(String!);
+ method public void setTransitionContent(String!, String!);
+ }
+
+ public interface CorePixelDp {
+ method public float toPixels(float);
+ }
+
+ public class Dimension {
+ method public void apply(androidx.constraintlayout.core.state.State!, androidx.constraintlayout.core.widgets.ConstraintWidget!, int);
+ method public static androidx.constraintlayout.core.state.Dimension! createFixed(int);
+ method public static androidx.constraintlayout.core.state.Dimension! createFixed(Object!);
+ method public static androidx.constraintlayout.core.state.Dimension! createParent();
+ method public static androidx.constraintlayout.core.state.Dimension! createPercent(Object!, float);
+ method public static androidx.constraintlayout.core.state.Dimension! createRatio(String!);
+ method public static androidx.constraintlayout.core.state.Dimension! createSpread();
+ method public static androidx.constraintlayout.core.state.Dimension! createSuggested(int);
+ method public static androidx.constraintlayout.core.state.Dimension! createSuggested(Object!);
+ method public static androidx.constraintlayout.core.state.Dimension! createWrap();
+ method public boolean equalsFixedValue(int);
+ method public androidx.constraintlayout.core.state.Dimension! fixed(int);
+ method public androidx.constraintlayout.core.state.Dimension! fixed(Object!);
+ method public androidx.constraintlayout.core.state.Dimension! max(int);
+ method public androidx.constraintlayout.core.state.Dimension! max(Object!);
+ method public androidx.constraintlayout.core.state.Dimension! min(int);
+ method public androidx.constraintlayout.core.state.Dimension! min(Object!);
+ method public androidx.constraintlayout.core.state.Dimension! percent(Object!, float);
+ method public androidx.constraintlayout.core.state.Dimension! ratio(String!);
+ method public androidx.constraintlayout.core.state.Dimension! suggested(int);
+ method public androidx.constraintlayout.core.state.Dimension! suggested(Object!);
+ field public static final Object! FIXED_DIMENSION;
+ field public static final Object! PARENT_DIMENSION;
+ field public static final Object! PERCENT_DIMENSION;
+ field public static final Object! RATIO_DIMENSION;
+ field public static final Object! SPREAD_DIMENSION;
+ field public static final Object! WRAP_DIMENSION;
+ }
+
+ public enum Dimension.Type {
+ enum_constant public static final androidx.constraintlayout.core.state.Dimension.Type FIXED;
+ enum_constant public static final androidx.constraintlayout.core.state.Dimension.Type MATCH_CONSTRAINT;
+ enum_constant public static final androidx.constraintlayout.core.state.Dimension.Type MATCH_PARENT;
+ enum_constant public static final androidx.constraintlayout.core.state.Dimension.Type WRAP;
+ }
+
+ public class HelperReference extends androidx.constraintlayout.core.state.ConstraintReference implements androidx.constraintlayout.core.state.helpers.Facade {
+ ctor public HelperReference(androidx.constraintlayout.core.state.State!, androidx.constraintlayout.core.state.State.Helper!);
+ method public androidx.constraintlayout.core.state.HelperReference! add(java.lang.Object!...!);
+ method public void applyBase();
+ method public androidx.constraintlayout.core.widgets.HelperWidget! getHelperWidget();
+ method public androidx.constraintlayout.core.state.State.Helper! getType();
+ method public void setHelperWidget(androidx.constraintlayout.core.widgets.HelperWidget!);
+ field protected final androidx.constraintlayout.core.state.State! mHelperState;
+ field protected java.util.ArrayList<java.lang.Object!>! mReferences;
+ }
+
+ public interface Interpolator {
+ method public float getInterpolation(float);
+ }
+
+ public interface Reference {
+ method public void apply();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getConstraintWidget();
+ method public androidx.constraintlayout.core.state.helpers.Facade! getFacade();
+ method public Object! getKey();
+ method public void setConstraintWidget(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void setKey(Object!);
+ }
+
+ public class Registry {
+ ctor public Registry();
+ method public String! currentContent(String!);
+ method public String! currentLayoutInformation(String!);
+ method public static androidx.constraintlayout.core.state.Registry! getInstance();
+ method public long getLastModified(String!);
+ method public java.util.Set<java.lang.String!>! getLayoutList();
+ method public void register(String!, androidx.constraintlayout.core.state.RegistryCallback!);
+ method public void setDrawDebug(String!, int);
+ method public void setLayoutInformationMode(String!, int);
+ method public void unregister(String!, androidx.constraintlayout.core.state.RegistryCallback!);
+ method public void updateContent(String!, String!);
+ method public void updateDimensions(String!, int, int);
+ method public void updateProgress(String!, float);
+ }
+
+ public interface RegistryCallback {
+ method public String! currentLayoutInformation();
+ method public String! currentMotionScene();
+ method public long getLastModified();
+ method public void onDimensions(int, int);
+ method public void onNewMotionScene(String!);
+ method public void onProgress(float);
+ method public void setDrawDebug(int);
+ method public void setLayoutInformationMode(int);
+ }
+
+ public class State {
+ ctor public State();
+ method public void apply(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ method public androidx.constraintlayout.core.state.helpers.BarrierReference! barrier(Object!, androidx.constraintlayout.core.state.State.Direction!);
+ method public void baselineNeededFor(Object!);
+ method public androidx.constraintlayout.core.state.helpers.AlignHorizontallyReference! centerHorizontally(java.lang.Object!...!);
+ method public androidx.constraintlayout.core.state.helpers.AlignVerticallyReference! centerVertically(java.lang.Object!...!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! constraints(Object!);
+ method public int convertDimension(Object!);
+ method public androidx.constraintlayout.core.state.ConstraintReference! createConstraintReference(Object!);
+ method public void directMapping();
+ method public androidx.constraintlayout.core.state.helpers.FlowReference! getFlow(Object!, boolean);
+ method public androidx.constraintlayout.core.state.helpers.GridReference getGrid(Object, String);
+ method public androidx.constraintlayout.core.state.helpers.FlowReference! getHorizontalFlow();
+ method public androidx.constraintlayout.core.state.helpers.FlowReference! getHorizontalFlow(java.lang.Object!...!);
+ method public java.util.ArrayList<java.lang.String!>! getIdsForTag(String!);
+ method public androidx.constraintlayout.core.state.helpers.FlowReference! getVerticalFlow();
+ method public androidx.constraintlayout.core.state.helpers.FlowReference! getVerticalFlow(java.lang.Object!...!);
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! guideline(Object!, int);
+ method public androidx.constraintlayout.core.state.State! height(androidx.constraintlayout.core.state.Dimension!);
+ method public androidx.constraintlayout.core.state.HelperReference! helper(Object!, androidx.constraintlayout.core.state.State.Helper!);
+ method public androidx.constraintlayout.core.state.helpers.HorizontalChainReference! horizontalChain();
+ method public androidx.constraintlayout.core.state.helpers.HorizontalChainReference! horizontalChain(java.lang.Object!...!);
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! horizontalGuideline(Object!);
+ method public boolean isBaselineNeeded(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method @Deprecated public boolean isLtr();
+ method public boolean isRtl();
+ method public void map(Object!, Object!);
+ method public void reset();
+ method public boolean sameFixedHeight(int);
+ method public boolean sameFixedWidth(int);
+ method public void setDpToPixel(androidx.constraintlayout.core.state.CorePixelDp!);
+ method public androidx.constraintlayout.core.state.State! setHeight(androidx.constraintlayout.core.state.Dimension!);
+ method @Deprecated public void setLtr(boolean);
+ method public void setRtl(boolean);
+ method public void setTag(String!, String!);
+ method public androidx.constraintlayout.core.state.State! setWidth(androidx.constraintlayout.core.state.Dimension!);
+ method public androidx.constraintlayout.core.state.helpers.VerticalChainReference! verticalChain();
+ method public androidx.constraintlayout.core.state.helpers.VerticalChainReference! verticalChain(java.lang.Object!...!);
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! verticalGuideline(Object!);
+ method public androidx.constraintlayout.core.state.State! width(androidx.constraintlayout.core.state.Dimension!);
+ field public static final Integer PARENT;
+ field protected java.util.HashMap<java.lang.Object!,androidx.constraintlayout.core.state.HelperReference!>! mHelperReferences;
+ field public final androidx.constraintlayout.core.state.ConstraintReference! mParent;
+ field protected java.util.HashMap<java.lang.Object!,androidx.constraintlayout.core.state.Reference!>! mReferences;
+ }
+
+ public enum State.Chain {
+ method public static androidx.constraintlayout.core.state.State.Chain! getChainByString(String!);
+ method public static int getValueByString(String!);
+ enum_constant public static final androidx.constraintlayout.core.state.State.Chain PACKED;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Chain SPREAD;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Chain SPREAD_INSIDE;
+ field public static java.util.Map<java.lang.String!,androidx.constraintlayout.core.state.State.Chain!>! chainMap;
+ field public static java.util.Map<java.lang.String!,java.lang.Integer!>! valueMap;
+ }
+
+ public enum State.Constraint {
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_TOP;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_TOP;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint CENTER_HORIZONTALLY;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint CENTER_VERTICALLY;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint CIRCULAR_CONSTRAINT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint END_TO_END;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint END_TO_START;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint LEFT_TO_LEFT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint LEFT_TO_RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint RIGHT_TO_LEFT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint RIGHT_TO_RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint START_TO_END;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint START_TO_START;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_TOP;
+ }
+
+ public enum State.Direction {
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction END;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction LEFT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction START;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Direction TOP;
+ }
+
+ public enum State.Helper {
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper ALIGN_HORIZONTALLY;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper ALIGN_VERTICALLY;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper BARRIER;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper COLUMN;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper FLOW;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper GRID;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper HORIZONTAL_CHAIN;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper HORIZONTAL_FLOW;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper LAYER;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper ROW;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper VERTICAL_CHAIN;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Helper VERTICAL_FLOW;
+ }
+
+ public enum State.Wrap {
+ method public static androidx.constraintlayout.core.state.State.Wrap! getChainByString(String!);
+ method public static int getValueByString(String!);
+ enum_constant public static final androidx.constraintlayout.core.state.State.Wrap ALIGNED;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Wrap CHAIN;
+ enum_constant public static final androidx.constraintlayout.core.state.State.Wrap NONE;
+ field public static java.util.Map<java.lang.String!,java.lang.Integer!>! valueMap;
+ field public static java.util.Map<java.lang.String!,androidx.constraintlayout.core.state.State.Wrap!>! wrapMap;
+ }
+
+ public class Transition implements androidx.constraintlayout.core.motion.utils.TypedValues {
+ ctor public Transition(androidx.constraintlayout.core.state.CorePixelDp);
+ method public void addCustomColor(int, String!, String!, int);
+ method public void addCustomFloat(int, String!, String!, float);
+ method public void addKeyAttribute(String!, androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void addKeyAttribute(String!, androidx.constraintlayout.core.motion.utils.TypedBundle!, androidx.constraintlayout.core.motion.CustomVariable![]!);
+ method public void addKeyCycle(String!, androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void addKeyPosition(String!, androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void addKeyPosition(String!, int, int, float, float);
+ method public void calcStagger();
+ method public void clear();
+ method public boolean contains(String!);
+ method public float dragToProgress(float, int, int, float, float);
+ method public void fillKeyPositions(androidx.constraintlayout.core.state.WidgetFrame!, float[]!, float[]!, float[]!);
+ method public androidx.constraintlayout.core.state.Transition.KeyPosition! findNextPosition(String!, int);
+ method public androidx.constraintlayout.core.state.Transition.KeyPosition! findPreviousPosition(String!, int);
+ method public int getAutoTransition();
+ method public androidx.constraintlayout.core.state.WidgetFrame! getEnd(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public androidx.constraintlayout.core.state.WidgetFrame! getEnd(String!);
+ method public int getId(String!);
+ method public androidx.constraintlayout.core.state.WidgetFrame! getInterpolated(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public androidx.constraintlayout.core.state.WidgetFrame! getInterpolated(String!);
+ method public int getInterpolatedHeight();
+ method public int getInterpolatedWidth();
+ method public androidx.constraintlayout.core.state.Interpolator! getInterpolator();
+ method public static androidx.constraintlayout.core.state.Interpolator! getInterpolator(int, String!);
+ method public int getKeyFrames(String!, float[]!, int[]!, int[]!);
+ method public androidx.constraintlayout.core.motion.Motion! getMotion(String!);
+ method public int getNumberKeyPositions(androidx.constraintlayout.core.state.WidgetFrame!);
+ method public float[]! getPath(String!);
+ method public androidx.constraintlayout.core.state.WidgetFrame! getStart(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public androidx.constraintlayout.core.state.WidgetFrame! getStart(String!);
+ method public float getTouchUpProgress(long);
+ method public androidx.constraintlayout.core.state.Transition.WidgetState! getWidgetState(String!, androidx.constraintlayout.core.widgets.ConstraintWidget!, int);
+ method public boolean hasOnSwipe();
+ method public boolean hasPositionKeyframes();
+ method public void interpolate(int, int, float);
+ method public boolean isEmpty();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public boolean isFirstDownAccepted(float, float);
+ method public boolean isTouchNotDone(float);
+ method public void setTouchUp(float, long, float, float);
+ method public void setTransitionProperties(androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public boolean setValue(int, boolean);
+ method public boolean setValue(int, float);
+ method public boolean setValue(int, int);
+ method public boolean setValue(int, String!);
+ method public void updateFrom(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, int);
+ field public static final int END = 1; // 0x1
+ field public static final int INTERPOLATED = 2; // 0x2
+ field public static final int START = 0; // 0x0
+ }
+
+ public static class Transition.WidgetState {
+ ctor public Transition.WidgetState();
+ method public androidx.constraintlayout.core.state.WidgetFrame! getFrame(int);
+ method public void interpolate(int, int, float, androidx.constraintlayout.core.state.Transition!);
+ method public void setKeyAttribute(androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void setKeyAttribute(androidx.constraintlayout.core.motion.utils.TypedBundle!, androidx.constraintlayout.core.motion.CustomVariable![]!);
+ method public void setKeyCycle(androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void setKeyPosition(androidx.constraintlayout.core.motion.utils.TypedBundle!);
+ method public void setPathRelative(androidx.constraintlayout.core.state.Transition.WidgetState!);
+ method public void update(androidx.constraintlayout.core.widgets.ConstraintWidget!, int);
+ }
+
+ public class TransitionParser {
+ ctor public TransitionParser();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static void parse(androidx.constraintlayout.core.parser.CLObject, androidx.constraintlayout.core.state.Transition) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method @Deprecated public static void parse(androidx.constraintlayout.core.parser.CLObject!, androidx.constraintlayout.core.state.Transition!, androidx.constraintlayout.core.state.CorePixelDp!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public static void parseKeyFrames(androidx.constraintlayout.core.parser.CLObject!, androidx.constraintlayout.core.state.Transition!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ }
+
+ public class WidgetFrame {
+ ctor public WidgetFrame();
+ ctor public WidgetFrame(androidx.constraintlayout.core.state.WidgetFrame!);
+ ctor public WidgetFrame(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void addCustomColor(String!, int);
+ method public void addCustomFloat(String!, float);
+ method public float centerX();
+ method public float centerY();
+ method public boolean containsCustom(String);
+ method public androidx.constraintlayout.core.motion.CustomVariable! getCustomAttribute(String!);
+ method public java.util.Set<java.lang.String!>! getCustomAttributeNames();
+ method public int getCustomColor(String!);
+ method public float getCustomFloat(String!);
+ method public String! getId();
+ method public androidx.constraintlayout.core.motion.utils.TypedBundle! getMotionProperties();
+ method public int height();
+ method public static void interpolate(int, int, androidx.constraintlayout.core.state.WidgetFrame!, androidx.constraintlayout.core.state.WidgetFrame!, androidx.constraintlayout.core.state.WidgetFrame!, androidx.constraintlayout.core.state.Transition!, float);
+ method public boolean isDefaultTransform();
+ method public StringBuilder! serialize(StringBuilder!);
+ method public StringBuilder! serialize(StringBuilder!, boolean);
+ method public void setCustomAttribute(String!, int, boolean);
+ method public void setCustomAttribute(String!, int, float);
+ method public void setCustomAttribute(String!, int, int);
+ method public void setCustomAttribute(String!, int, String!);
+ method public void setCustomValue(androidx.constraintlayout.core.motion.CustomAttribute!, float[]!);
+ method public boolean setValue(String!, androidx.constraintlayout.core.parser.CLElement!) throws androidx.constraintlayout.core.parser.CLParsingException;
+ method public androidx.constraintlayout.core.state.WidgetFrame! update();
+ method public androidx.constraintlayout.core.state.WidgetFrame! update(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void updateAttributes(androidx.constraintlayout.core.state.WidgetFrame!);
+ method public int width();
+ field public float alpha;
+ field public int bottom;
+ field public float interpolatedPos;
+ field public int left;
+ field public String! name;
+ field public static float phone_orientation;
+ field public float pivotX;
+ field public float pivotY;
+ field public int right;
+ field public float rotationX;
+ field public float rotationY;
+ field public float rotationZ;
+ field public float scaleX;
+ field public float scaleY;
+ field public int top;
+ field public float translationX;
+ field public float translationY;
+ field public float translationZ;
+ field public int visibility;
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget! widget;
+ }
+
+}
+
+package androidx.constraintlayout.core.state.helpers {
+
+ public class AlignHorizontallyReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public AlignHorizontallyReference(androidx.constraintlayout.core.state.State!);
+ }
+
+ public class AlignVerticallyReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public AlignVerticallyReference(androidx.constraintlayout.core.state.State!);
+ }
+
+ public class BarrierReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public BarrierReference(androidx.constraintlayout.core.state.State!);
+ method public void setBarrierDirection(androidx.constraintlayout.core.state.State.Direction!);
+ }
+
+ public class ChainReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public ChainReference(androidx.constraintlayout.core.state.State, androidx.constraintlayout.core.state.State.Helper);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void addChainElement(Object, float, float, float, float, float);
+ method public void addChainElement(String, float, float, float);
+ method public androidx.constraintlayout.core.state.helpers.ChainReference bias(float);
+ method public float getBias();
+ method protected float getPostMargin(String);
+ method protected float getPreMargin(String);
+ method public androidx.constraintlayout.core.state.State.Chain getStyle();
+ method protected float getWeight(String);
+ method public androidx.constraintlayout.core.state.helpers.ChainReference style(androidx.constraintlayout.core.state.State.Chain);
+ field protected float mBias;
+ field @Deprecated protected java.util.HashMap<java.lang.String!,java.lang.Float!> mMapPostMargin;
+ field @Deprecated protected java.util.HashMap<java.lang.String!,java.lang.Float!> mMapPreMargin;
+ field @Deprecated protected java.util.HashMap<java.lang.String!,java.lang.Float!> mMapWeights;
+ field protected androidx.constraintlayout.core.state.State.Chain mStyle;
+ }
+
+ public interface Facade {
+ method public void apply();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getConstraintWidget();
+ }
+
+ public class FlowReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public FlowReference(androidx.constraintlayout.core.state.State!, androidx.constraintlayout.core.state.State.Helper!);
+ method public void addFlowElement(String!, float, float, float);
+ method public float getFirstHorizontalBias();
+ method public int getFirstHorizontalStyle();
+ method public float getFirstVerticalBias();
+ method public int getFirstVerticalStyle();
+ method public int getHorizontalAlign();
+ method public float getHorizontalBias();
+ method public int getHorizontalGap();
+ method public int getHorizontalStyle();
+ method public float getLastHorizontalBias();
+ method public int getLastHorizontalStyle();
+ method public float getLastVerticalBias();
+ method public int getLastVerticalStyle();
+ method public int getMaxElementsWrap();
+ method public int getOrientation();
+ method public int getPaddingBottom();
+ method public int getPaddingLeft();
+ method public int getPaddingRight();
+ method public int getPaddingTop();
+ method protected float getPostMargin(String!);
+ method protected float getPreMargin(String!);
+ method public int getVerticalAlign();
+ method public float getVerticalBias();
+ method public int getVerticalGap();
+ method public int getVerticalStyle();
+ method protected float getWeight(String!);
+ method public int getWrapMode();
+ method public void setFirstHorizontalBias(float);
+ method public void setFirstHorizontalStyle(int);
+ method public void setFirstVerticalBias(float);
+ method public void setFirstVerticalStyle(int);
+ method public void setHorizontalAlign(int);
+ method public void setHorizontalGap(int);
+ method public void setHorizontalStyle(int);
+ method public void setLastHorizontalBias(float);
+ method public void setLastHorizontalStyle(int);
+ method public void setLastVerticalBias(float);
+ method public void setLastVerticalStyle(int);
+ method public void setMaxElementsWrap(int);
+ method public void setOrientation(int);
+ method public void setPaddingBottom(int);
+ method public void setPaddingLeft(int);
+ method public void setPaddingRight(int);
+ method public void setPaddingTop(int);
+ method public void setVerticalAlign(int);
+ method public void setVerticalGap(int);
+ method public void setVerticalStyle(int);
+ method public void setWrapMode(int);
+ field protected float mFirstHorizontalBias;
+ field protected int mFirstHorizontalStyle;
+ field protected float mFirstVerticalBias;
+ field protected int mFirstVerticalStyle;
+ field protected androidx.constraintlayout.core.widgets.Flow! mFlow;
+ field protected int mHorizontalAlign;
+ field protected int mHorizontalGap;
+ field protected int mHorizontalStyle;
+ field protected float mLastHorizontalBias;
+ field protected int mLastHorizontalStyle;
+ field protected float mLastVerticalBias;
+ field protected int mLastVerticalStyle;
+ field protected java.util.HashMap<java.lang.String!,java.lang.Float!>! mMapPostMargin;
+ field protected java.util.HashMap<java.lang.String!,java.lang.Float!>! mMapPreMargin;
+ field protected java.util.HashMap<java.lang.String!,java.lang.Float!>! mMapWeights;
+ field protected int mMaxElementsWrap;
+ field protected int mOrientation;
+ field protected int mPaddingBottom;
+ field protected int mPaddingLeft;
+ field protected int mPaddingRight;
+ field protected int mPaddingTop;
+ field protected int mVerticalAlign;
+ field protected int mVerticalGap;
+ field protected int mVerticalStyle;
+ field protected int mWrapMode;
+ }
+
+ public class GridReference extends androidx.constraintlayout.core.state.HelperReference {
+ ctor public GridReference(androidx.constraintlayout.core.state.State, androidx.constraintlayout.core.state.State.Helper);
+ method public String? getColumnWeights();
+ method public int getColumnsSet();
+ method public int getFlags();
+ method public float getHorizontalGaps();
+ method public int getOrientation();
+ method public int getPaddingBottom();
+ method public int getPaddingEnd();
+ method public int getPaddingStart();
+ method public int getPaddingTop();
+ method public String? getRowWeights();
+ method public int getRowsSet();
+ method public String? getSkips();
+ method public String? getSpans();
+ method public float getVerticalGaps();
+ method public void setColumnWeights(String);
+ method public void setColumnsSet(int);
+ method public void setFlags(int);
+ method public void setFlags(String);
+ method public void setHorizontalGaps(float);
+ method public void setOrientation(int);
+ method public void setPaddingBottom(int);
+ method public void setPaddingEnd(int);
+ method public void setPaddingStart(int);
+ method public void setPaddingTop(int);
+ method public void setRowWeights(String);
+ method public void setRowsSet(int);
+ method public void setSkips(String);
+ method public void setSpans(String);
+ method public void setVerticalGaps(float);
+ }
+
+ public class GuidelineReference implements androidx.constraintlayout.core.state.helpers.Facade androidx.constraintlayout.core.state.Reference {
+ ctor public GuidelineReference(androidx.constraintlayout.core.state.State!);
+ method public void apply();
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! end(Object!);
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getConstraintWidget();
+ method public androidx.constraintlayout.core.state.helpers.Facade! getFacade();
+ method public Object! getKey();
+ method public int getOrientation();
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! percent(float);
+ method public void setConstraintWidget(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void setKey(Object!);
+ method public void setOrientation(int);
+ method public androidx.constraintlayout.core.state.helpers.GuidelineReference! start(Object!);
+ }
+
+ public class HorizontalChainReference extends androidx.constraintlayout.core.state.helpers.ChainReference {
+ ctor public HorizontalChainReference(androidx.constraintlayout.core.state.State!);
+ }
+
+ public class VerticalChainReference extends androidx.constraintlayout.core.state.helpers.ChainReference {
+ ctor public VerticalChainReference(androidx.constraintlayout.core.state.State!);
+ }
+
+}
+
+package androidx.constraintlayout.core.utils {
+
+ public class GridCore extends androidx.constraintlayout.core.widgets.VirtualLayout {
+ ctor public GridCore();
+ ctor public GridCore(int, int);
+ method public String? getColumnWeights();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidgetContainer? getContainer();
+ method public int getFlags();
+ method public float getHorizontalGaps();
+ method public int getOrientation();
+ method public String? getRowWeights();
+ method public float getVerticalGaps();
+ method public void setColumnWeights(String);
+ method public void setColumns(int);
+ method public void setContainer(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer);
+ method public void setFlags(int);
+ method public void setHorizontalGaps(float);
+ method public void setOrientation(int);
+ method public void setRowWeights(String);
+ method public void setRows(int);
+ method public void setSkips(String);
+ method public void setSpans(CharSequence);
+ method public void setVerticalGaps(float);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int SPANS_RESPECT_WIDGET_ORDER = 2; // 0x2
+ field public static final int SUB_GRID_BY_COL_ROW = 1; // 0x1
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public class GridEngine {
+ ctor public GridEngine();
+ ctor public GridEngine(int, int);
+ ctor public GridEngine(int, int, int);
+ method public int bottomOfWidget(int);
+ method public int leftOfWidget(int);
+ method public int rightOfWidget(int);
+ method public void setColumns(int);
+ method public void setNumWidgets(int);
+ method public void setOrientation(int);
+ method public void setRows(int);
+ method public void setSkips(String!);
+ method public void setSpans(CharSequence!);
+ method public void setup();
+ method public int topOfWidget(int);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+}
+
+package androidx.constraintlayout.core.widgets {
+
+ public class Barrier extends androidx.constraintlayout.core.widgets.HelperWidget {
+ ctor public Barrier();
+ ctor public Barrier(String!);
+ method public boolean allSolved();
+ method @Deprecated public boolean allowsGoneWidget();
+ method public boolean getAllowsGoneWidget();
+ method public int getBarrierType();
+ method public int getMargin();
+ method public int getOrientation();
+ method protected void markWidgets();
+ method public void setAllowsGoneWidget(boolean);
+ method public void setBarrierType(int);
+ method public void setMargin(int);
+ field public static final int BOTTOM = 3; // 0x3
+ field public static final int LEFT = 0; // 0x0
+ field public static final int RIGHT = 1; // 0x1
+ field public static final int TOP = 2; // 0x2
+ }
+
+ public class Chain {
+ ctor public Chain();
+ method public static void applyChainConstraints(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.LinearSystem!, java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintWidget!>!, int);
+ field public static final boolean USE_CHAIN_OPTIMIZATION = false;
+ }
+
+ public class ChainHead {
+ ctor public ChainHead(androidx.constraintlayout.core.widgets.ConstraintWidget!, int, boolean);
+ method public void define();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getFirst();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getFirstMatchConstraintWidget();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getFirstVisibleWidget();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getHead();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getLast();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getLastMatchConstraintWidget();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getLastVisibleWidget();
+ method public float getTotalWeight();
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mFirst;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mFirstMatchConstraintWidget;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mFirstVisibleWidget;
+ field protected boolean mHasComplexMatchWeights;
+ field protected boolean mHasDefinedWeights;
+ field protected boolean mHasRatio;
+ field protected boolean mHasUndefinedWeights;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mHead;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mLast;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mLastMatchConstraintWidget;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget! mLastVisibleWidget;
+ field protected float mTotalWeight;
+ field protected java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintWidget!>! mWeightedMatchConstraintsWidgets;
+ field protected int mWidgetsCount;
+ field protected int mWidgetsMatchCount;
+ }
+
+ public class ConstraintAnchor {
+ ctor public ConstraintAnchor(androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!);
+ method public boolean connect(androidx.constraintlayout.core.widgets.ConstraintAnchor!, int);
+ method public boolean connect(androidx.constraintlayout.core.widgets.ConstraintAnchor!, int, int, boolean);
+ method public void copyFrom(androidx.constraintlayout.core.widgets.ConstraintAnchor!, java.util.HashMap<androidx.constraintlayout.core.widgets.ConstraintWidget!,androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public void findDependents(int, java.util.ArrayList<androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!>!, androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!);
+ method public java.util.HashSet<androidx.constraintlayout.core.widgets.ConstraintAnchor!>! getDependents();
+ method public int getFinalValue();
+ method public int getMargin();
+ method public final androidx.constraintlayout.core.widgets.ConstraintAnchor! getOpposite();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getOwner();
+ method public androidx.constraintlayout.core.SolverVariable! getSolverVariable();
+ method public androidx.constraintlayout.core.widgets.ConstraintAnchor! getTarget();
+ method public androidx.constraintlayout.core.widgets.ConstraintAnchor.Type! getType();
+ method public boolean hasCenteredDependents();
+ method public boolean hasDependents();
+ method public boolean hasFinalValue();
+ method public boolean isConnected();
+ method public boolean isConnectionAllowed(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public boolean isConnectionAllowed(androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public boolean isSideAnchor();
+ method public boolean isSimilarDimensionConnection(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public boolean isValidConnection(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public boolean isVerticalAnchor();
+ method public void reset();
+ method public void resetFinalResolution();
+ method public void resetSolverVariable(androidx.constraintlayout.core.Cache!);
+ method public void setFinalValue(int);
+ method public void setGoneMargin(int);
+ method public void setMargin(int);
+ field public int mMargin;
+ field public final androidx.constraintlayout.core.widgets.ConstraintWidget! mOwner;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mTarget;
+ field public final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type! mType;
+ }
+
+ public enum ConstraintAnchor.Type {
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type BASELINE;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type BOTTOM;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type CENTER;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type CENTER_X;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type CENTER_Y;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type LEFT;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type NONE;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type RIGHT;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintAnchor.Type TOP;
+ }
+
+ public class ConstraintWidget {
+ ctor public ConstraintWidget();
+ ctor public ConstraintWidget(int, int);
+ ctor public ConstraintWidget(int, int, int, int);
+ ctor public ConstraintWidget(String!);
+ ctor public ConstraintWidget(String!, int, int);
+ ctor public ConstraintWidget(String!, int, int, int, int);
+ method public void addChildrenToSolverByDependency(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.LinearSystem!, java.util.HashSet<androidx.constraintlayout.core.widgets.ConstraintWidget!>!, int, boolean);
+ method public void addToSolver(androidx.constraintlayout.core.LinearSystem!, boolean);
+ method public boolean allowedInBarrier();
+ method public void connect(androidx.constraintlayout.core.widgets.ConstraintAnchor!, androidx.constraintlayout.core.widgets.ConstraintAnchor!, int);
+ method public void connect(androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!);
+ method public void connect(androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, int);
+ method public void connectCircularConstraint(androidx.constraintlayout.core.widgets.ConstraintWidget!, float, int);
+ method public void copy(androidx.constraintlayout.core.widgets.ConstraintWidget!, java.util.HashMap<androidx.constraintlayout.core.widgets.ConstraintWidget!,androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public void createObjectVariables(androidx.constraintlayout.core.LinearSystem!);
+ method public void ensureMeasureRequested();
+ method public void ensureWidgetRuns();
+ method public androidx.constraintlayout.core.widgets.ConstraintAnchor! getAnchor(androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!);
+ method public java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintAnchor!>! getAnchors();
+ method public int getBaselineDistance();
+ method public float getBiasPercent(int);
+ method public int getBottom();
+ method public Object! getCompanionWidget();
+ method public int getContainerItemSkip();
+ method public String! getDebugName();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! getDimensionBehaviour(int);
+ method public float getDimensionRatio();
+ method public int getDimensionRatioSide();
+ method public boolean getHasBaseline();
+ method public int getHeight();
+ method public float getHorizontalBiasPercent();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getHorizontalChainControlWidget();
+ method public int getHorizontalChainStyle();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! getHorizontalDimensionBehaviour();
+ method public int getHorizontalMargin();
+ method public int getLastHorizontalMeasureSpec();
+ method public int getLastVerticalMeasureSpec();
+ method public int getLeft();
+ method public int getLength(int);
+ method public int getMaxHeight();
+ method public int getMaxWidth();
+ method public int getMinHeight();
+ method public int getMinWidth();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getNextChainMember(int);
+ method public int getOptimizerWrapHeight();
+ method public int getOptimizerWrapWidth();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getParent();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getPreviousChainMember(int);
+ method public int getRight();
+ method protected int getRootX();
+ method protected int getRootY();
+ method public androidx.constraintlayout.core.widgets.analyzer.WidgetRun! getRun(int);
+ method public void getSceneString(StringBuilder!);
+ method public int getTop();
+ method public String! getType();
+ method public float getVerticalBiasPercent();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getVerticalChainControlWidget();
+ method public int getVerticalChainStyle();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! getVerticalDimensionBehaviour();
+ method public int getVerticalMargin();
+ method public int getVisibility();
+ method public int getWidth();
+ method public int getWrapBehaviorInParent();
+ method public int getX();
+ method public int getY();
+ method public boolean hasBaseline();
+ method public boolean hasDanglingDimension(int);
+ method public boolean hasDependencies();
+ method public boolean hasDimensionOverride();
+ method public boolean hasResolvedTargets(int, int);
+ method public void immediateConnect(androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, int, int);
+ method public boolean isAnimated();
+ method public boolean isHeightWrapContent();
+ method public boolean isHorizontalSolvingPassDone();
+ method public boolean isInBarrier(int);
+ method public boolean isInHorizontalChain();
+ method public boolean isInPlaceholder();
+ method public boolean isInVerticalChain();
+ method public boolean isInVirtualLayout();
+ method public boolean isMeasureRequested();
+ method public boolean isResolvedHorizontally();
+ method public boolean isResolvedVertically();
+ method public boolean isRoot();
+ method public boolean isSpreadHeight();
+ method public boolean isSpreadWidth();
+ method public boolean isVerticalSolvingPassDone();
+ method public boolean isWidthWrapContent();
+ method public void markHorizontalSolvingPassDone();
+ method public void markVerticalSolvingPassDone();
+ method public boolean oppositeDimensionDependsOn(int);
+ method public boolean oppositeDimensionsTied();
+ method public void reset();
+ method public void resetAllConstraints();
+ method public void resetAnchor(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public void resetAnchors();
+ method public void resetFinalResolution();
+ method public void resetSolverVariables(androidx.constraintlayout.core.Cache!);
+ method public void resetSolvingPassFlag();
+ method public StringBuilder! serialize(StringBuilder!);
+ method public void setAnimated(boolean);
+ method public void setBaselineDistance(int);
+ method public void setCompanionWidget(Object!);
+ method public void setContainerItemSkip(int);
+ method public void setDebugName(String!);
+ method public void setDebugSolverName(androidx.constraintlayout.core.LinearSystem!, String!);
+ method public void setDimension(int, int);
+ method public void setDimensionRatio(float, int);
+ method public void setDimensionRatio(String!);
+ method public void setFinalBaseline(int);
+ method public void setFinalFrame(int, int, int, int, int, int);
+ method public void setFinalHorizontal(int, int);
+ method public void setFinalLeft(int);
+ method public void setFinalTop(int);
+ method public void setFinalVertical(int, int);
+ method public void setFrame(int, int, int);
+ method public void setFrame(int, int, int, int);
+ method public void setGoneMargin(androidx.constraintlayout.core.widgets.ConstraintAnchor.Type!, int);
+ method public void setHasBaseline(boolean);
+ method public void setHeight(int);
+ method public void setHeightWrapContent(boolean);
+ method public void setHorizontalBiasPercent(float);
+ method public void setHorizontalChainStyle(int);
+ method public void setHorizontalDimension(int, int);
+ method public void setHorizontalDimensionBehaviour(androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!);
+ method public void setHorizontalMatchStyle(int, int, int, float);
+ method public void setHorizontalWeight(float);
+ method protected void setInBarrier(int, boolean);
+ method public void setInPlaceholder(boolean);
+ method public void setInVirtualLayout(boolean);
+ method public void setLastMeasureSpec(int, int);
+ method public void setLength(int, int);
+ method public void setMaxHeight(int);
+ method public void setMaxWidth(int);
+ method public void setMeasureRequested(boolean);
+ method public void setMinHeight(int);
+ method public void setMinWidth(int);
+ method public void setOffset(int, int);
+ method public void setOrigin(int, int);
+ method public void setParent(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void setType(String!);
+ method public void setVerticalBiasPercent(float);
+ method public void setVerticalChainStyle(int);
+ method public void setVerticalDimension(int, int);
+ method public void setVerticalDimensionBehaviour(androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!);
+ method public void setVerticalMatchStyle(int, int, int, float);
+ method public void setVerticalWeight(float);
+ method public void setVisibility(int);
+ method public void setWidth(int);
+ method public void setWidthWrapContent(boolean);
+ method public void setWrapBehaviorInParent(int);
+ method public void setX(int);
+ method public void setY(int);
+ method public void setupDimensionRatio(boolean, boolean, boolean, boolean);
+ method public void updateFromRuns(boolean, boolean);
+ method public void updateFromSolver(androidx.constraintlayout.core.LinearSystem!, boolean);
+ field public static final int ANCHOR_BASELINE = 4; // 0x4
+ field public static final int ANCHOR_BOTTOM = 3; // 0x3
+ field public static final int ANCHOR_LEFT = 0; // 0x0
+ field public static final int ANCHOR_RIGHT = 1; // 0x1
+ field public static final int ANCHOR_TOP = 2; // 0x2
+ field public static final int BOTH = 2; // 0x2
+ field public static final int CHAIN_PACKED = 2; // 0x2
+ field public static final int CHAIN_SPREAD = 0; // 0x0
+ field public static final int CHAIN_SPREAD_INSIDE = 1; // 0x1
+ field public static float DEFAULT_BIAS;
+ field protected static final int DIRECT = 2; // 0x2
+ field public static final int GONE = 8; // 0x8
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int INVISIBLE = 4; // 0x4
+ field public static final int MATCH_CONSTRAINT_PERCENT = 2; // 0x2
+ field public static final int MATCH_CONSTRAINT_RATIO = 3; // 0x3
+ field public static final int MATCH_CONSTRAINT_RATIO_RESOLVED = 4; // 0x4
+ field public static final int MATCH_CONSTRAINT_SPREAD = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_WRAP = 1; // 0x1
+ field protected static final int SOLVER = 1; // 0x1
+ field public static final int UNKNOWN = -1; // 0xffffffff
+ field public static final int VERTICAL = 1; // 0x1
+ field public static final int VISIBLE = 0; // 0x0
+ field public static final int WRAP_BEHAVIOR_HORIZONTAL_ONLY = 1; // 0x1
+ field public static final int WRAP_BEHAVIOR_INCLUDED = 0; // 0x0
+ field public static final int WRAP_BEHAVIOR_SKIPPED = 3; // 0x3
+ field public static final int WRAP_BEHAVIOR_VERTICAL_ONLY = 2; // 0x2
+ field public androidx.constraintlayout.core.state.WidgetFrame! frame;
+ field public androidx.constraintlayout.core.widgets.analyzer.ChainRun! horizontalChainRun;
+ field public int horizontalGroup;
+ field public boolean[]! isTerminalWidget;
+ field protected java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintAnchor!>! mAnchors;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mBaseline;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mBottom;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mCenter;
+ field public float mCircleConstraintAngle;
+ field public float mDimensionRatio;
+ field protected int mDimensionRatioSide;
+ field public int mHorizontalResolution;
+ field public androidx.constraintlayout.core.widgets.analyzer.HorizontalWidgetRun! mHorizontalRun;
+ field public boolean mIsHeightWrapContent;
+ field public boolean mIsWidthWrapContent;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mLeft;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor![]! mListAnchors;
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour![]! mListDimensionBehaviors;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget![]! mListNextMatchConstraintsWidget;
+ field public int mMatchConstraintDefaultHeight;
+ field public int mMatchConstraintDefaultWidth;
+ field public int mMatchConstraintMaxHeight;
+ field public int mMatchConstraintMaxWidth;
+ field public int mMatchConstraintMinHeight;
+ field public int mMatchConstraintMinWidth;
+ field public float mMatchConstraintPercentHeight;
+ field public float mMatchConstraintPercentWidth;
+ field protected int mMinHeight;
+ field protected int mMinWidth;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget![]! mNextChainWidget;
+ field protected int mOffsetX;
+ field protected int mOffsetY;
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget! mParent;
+ field public int[]! mResolvedMatchConstraintDefault;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mRight;
+ field public androidx.constraintlayout.core.widgets.ConstraintAnchor! mTop;
+ field public int mVerticalResolution;
+ field public androidx.constraintlayout.core.widgets.analyzer.VerticalWidgetRun! mVerticalRun;
+ field public float[]! mWeight;
+ field protected int mX;
+ field protected int mY;
+ field public boolean measured;
+ field public androidx.constraintlayout.core.widgets.analyzer.WidgetRun![]! run;
+ field public String! stringId;
+ field public androidx.constraintlayout.core.widgets.analyzer.ChainRun! verticalChainRun;
+ field public int verticalGroup;
+ }
+
+ public enum ConstraintWidget.DimensionBehaviour {
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour FIXED;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour MATCH_CONSTRAINT;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour MATCH_PARENT;
+ enum_constant public static final androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour WRAP_CONTENT;
+ }
+
+ public class ConstraintWidgetContainer extends androidx.constraintlayout.core.widgets.WidgetContainer {
+ ctor public ConstraintWidgetContainer();
+ ctor public ConstraintWidgetContainer(int, int);
+ ctor public ConstraintWidgetContainer(int, int, int, int);
+ ctor public ConstraintWidgetContainer(String!, int, int);
+ method public boolean addChildrenToSolver(androidx.constraintlayout.core.LinearSystem!);
+ method public void addHorizontalWrapMaxVariable(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public void addHorizontalWrapMinVariable(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method public void defineTerminalWidgets();
+ method public boolean directMeasure(boolean);
+ method public boolean directMeasureSetup(boolean);
+ method public boolean directMeasureWithOrientation(boolean, int);
+ method public void fillMetrics(androidx.constraintlayout.core.Metrics!);
+ method public java.util.ArrayList<androidx.constraintlayout.core.widgets.Guideline!>! getHorizontalGuidelines();
+ method public androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer! getMeasurer();
+ method public int getOptimizationLevel();
+ method public androidx.constraintlayout.core.LinearSystem! getSystem();
+ method public java.util.ArrayList<androidx.constraintlayout.core.widgets.Guideline!>! getVerticalGuidelines();
+ method public boolean handlesInternalConstraints();
+ method public void invalidateGraph();
+ method public void invalidateMeasures();
+ method public boolean isHeightMeasuredTooSmall();
+ method public boolean isRtl();
+ method public boolean isWidthMeasuredTooSmall();
+ method public static boolean measure(int, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer!, androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measure!, int);
+ method public long measure(int, int, int, int, int, int, int, int, int);
+ method public boolean optimizeFor(int);
+ method public void setMeasurer(androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer!);
+ method public void setOptimizationLevel(int);
+ method public void setPadding(int, int, int, int);
+ method public void setPass(int);
+ method public void setRtl(boolean);
+ method public boolean updateChildrenFromSolver(androidx.constraintlayout.core.LinearSystem!, boolean[]!);
+ method public void updateHierarchy();
+ field public androidx.constraintlayout.core.widgets.analyzer.DependencyGraph! mDependencyGraph;
+ field public boolean mGroupsWrapOptimized;
+ field public int mHorizontalChainsSize;
+ field public boolean mHorizontalWrapOptimized;
+ field public androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measure! mMeasure;
+ field protected androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer! mMeasurer;
+ field public androidx.constraintlayout.core.Metrics! mMetrics;
+ field public boolean mSkipSolver;
+ field protected androidx.constraintlayout.core.LinearSystem! mSystem;
+ field public int mVerticalChainsSize;
+ field public boolean mVerticalWrapOptimized;
+ field public int mWrapFixedHeight;
+ field public int mWrapFixedWidth;
+ }
+
+ public class Flow extends androidx.constraintlayout.core.widgets.VirtualLayout {
+ ctor public Flow();
+ method public float getMaxElementsWrap();
+ method public void setFirstHorizontalBias(float);
+ method public void setFirstHorizontalStyle(int);
+ method public void setFirstVerticalBias(float);
+ method public void setFirstVerticalStyle(int);
+ method public void setHorizontalAlign(int);
+ method public void setHorizontalBias(float);
+ method public void setHorizontalGap(int);
+ method public void setHorizontalStyle(int);
+ method public void setLastHorizontalBias(float);
+ method public void setLastHorizontalStyle(int);
+ method public void setLastVerticalBias(float);
+ method public void setLastVerticalStyle(int);
+ method public void setMaxElementsWrap(int);
+ method public void setOrientation(int);
+ method public void setVerticalAlign(int);
+ method public void setVerticalBias(float);
+ method public void setVerticalGap(int);
+ method public void setVerticalStyle(int);
+ method public void setWrapMode(int);
+ field public static final int HORIZONTAL_ALIGN_CENTER = 2; // 0x2
+ field public static final int HORIZONTAL_ALIGN_END = 1; // 0x1
+ field public static final int HORIZONTAL_ALIGN_START = 0; // 0x0
+ field public static final int VERTICAL_ALIGN_BASELINE = 3; // 0x3
+ field public static final int VERTICAL_ALIGN_BOTTOM = 1; // 0x1
+ field public static final int VERTICAL_ALIGN_CENTER = 2; // 0x2
+ field public static final int VERTICAL_ALIGN_TOP = 0; // 0x0
+ field public static final int WRAP_ALIGNED = 2; // 0x2
+ field public static final int WRAP_CHAIN = 1; // 0x1
+ field public static final int WRAP_CHAIN_NEW = 3; // 0x3
+ field public static final int WRAP_NONE = 0; // 0x0
+ }
+
+ public class Guideline extends androidx.constraintlayout.core.widgets.ConstraintWidget {
+ ctor public Guideline();
+ method public void cyclePosition();
+ method public androidx.constraintlayout.core.widgets.ConstraintAnchor! getAnchor();
+ method public int getMinimumPosition();
+ method public int getOrientation();
+ method public int getRelativeBegin();
+ method public int getRelativeBehaviour();
+ method public int getRelativeEnd();
+ method public float getRelativePercent();
+ method public boolean isPercent();
+ method public void setFinalValue(int);
+ method public void setGuideBegin(int);
+ method public void setGuideEnd(int);
+ method public void setGuidePercent(float);
+ method public void setGuidePercent(int);
+ method public void setMinimumPosition(int);
+ method public void setOrientation(int);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int RELATIVE_BEGIN = 1; // 0x1
+ field public static final int RELATIVE_END = 2; // 0x2
+ field public static final int RELATIVE_PERCENT = 0; // 0x0
+ field public static final int RELATIVE_UNKNOWN = -1; // 0xffffffff
+ field public static final int VERTICAL = 1; // 0x1
+ field protected boolean mGuidelineUseRtl;
+ field protected int mRelativeBegin;
+ field protected int mRelativeEnd;
+ field protected float mRelativePercent;
+ }
+
+ public interface Helper {
+ method public void add(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void removeAllIds();
+ method public void updateConstraints(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ }
+
+ public class HelperWidget extends androidx.constraintlayout.core.widgets.ConstraintWidget implements androidx.constraintlayout.core.widgets.Helper {
+ ctor public HelperWidget();
+ method public void add(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void addDependents(java.util.ArrayList<androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!>!, int, androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!);
+ method public int findGroupInDependents(int);
+ method public void removeAllIds();
+ method public void updateConstraints(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget![]! mWidgets;
+ field public int mWidgetsCount;
+ }
+
+ public class Optimizer {
+ ctor public Optimizer();
+ method public static final boolean enabled(int, int);
+ field public static final int OPTIMIZATION_BARRIER = 2; // 0x2
+ field public static final int OPTIMIZATION_CACHE_MEASURES = 256; // 0x100
+ field public static final int OPTIMIZATION_CHAIN = 4; // 0x4
+ field public static final int OPTIMIZATION_DEPENDENCY_ORDERING = 512; // 0x200
+ field public static final int OPTIMIZATION_DIMENSIONS = 8; // 0x8
+ field public static final int OPTIMIZATION_DIRECT = 1; // 0x1
+ field public static final int OPTIMIZATION_GRAPH = 64; // 0x40
+ field public static final int OPTIMIZATION_GRAPH_WRAP = 128; // 0x80
+ field public static final int OPTIMIZATION_GROUPING = 1024; // 0x400
+ field public static final int OPTIMIZATION_GROUPS = 32; // 0x20
+ field public static final int OPTIMIZATION_NONE = 0; // 0x0
+ field public static final int OPTIMIZATION_RATIO = 16; // 0x10
+ field public static final int OPTIMIZATION_STANDARD = 257; // 0x101
+ }
+
+ public class Placeholder extends androidx.constraintlayout.core.widgets.VirtualLayout {
+ ctor public Placeholder();
+ }
+
+ public class Rectangle {
+ ctor public Rectangle();
+ method public boolean contains(int, int);
+ method public int getCenterX();
+ method public int getCenterY();
+ method public void setBounds(int, int, int, int);
+ field public int height;
+ field public int width;
+ field public int x;
+ field public int y;
+ }
+
+ public class VirtualLayout extends androidx.constraintlayout.core.widgets.HelperWidget {
+ ctor public VirtualLayout();
+ method public void applyRtl(boolean);
+ method public void captureWidgets();
+ method public boolean contains(java.util.HashSet<androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public int getMeasuredHeight();
+ method public int getMeasuredWidth();
+ method public int getPaddingBottom();
+ method public int getPaddingLeft();
+ method public int getPaddingRight();
+ method public int getPaddingTop();
+ method protected void measure(androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, int, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, int);
+ method public void measure(int, int, int, int);
+ method protected boolean measureChildren();
+ method public boolean needSolverPass();
+ method protected void needsCallbackFromSolver(boolean);
+ method public void setMeasure(int, int);
+ method public void setPadding(int);
+ method public void setPaddingBottom(int);
+ method public void setPaddingEnd(int);
+ method public void setPaddingLeft(int);
+ method public void setPaddingRight(int);
+ method public void setPaddingStart(int);
+ method public void setPaddingTop(int);
+ field protected androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measure! mMeasure;
+ }
+
+ public class WidgetContainer extends androidx.constraintlayout.core.widgets.ConstraintWidget {
+ ctor public WidgetContainer();
+ ctor public WidgetContainer(int, int);
+ ctor public WidgetContainer(int, int, int, int);
+ method public void add(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void add(androidx.constraintlayout.core.widgets.ConstraintWidget!...!);
+ method public java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintWidget!>! getChildren();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidgetContainer! getRootConstraintContainer();
+ method public void layout();
+ method public void remove(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void removeAllChildren();
+ field public java.util.ArrayList<androidx.constraintlayout.core.widgets.ConstraintWidget!>! mChildren;
+ }
+
+}
+
+package androidx.constraintlayout.core.widgets.analyzer {
+
+ public class BasicMeasure {
+ ctor public BasicMeasure(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ method public long solverMeasure(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, int, int, int, int, int, int, int, int, int);
+ method public void updateHierarchy(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ field public static final int AT_MOST = -2147483648; // 0x80000000
+ field public static final int EXACTLY = 1073741824; // 0x40000000
+ field public static final int FIXED = -3; // 0xfffffffd
+ field public static final int MATCH_PARENT = -1; // 0xffffffff
+ field public static final int UNSPECIFIED = 0; // 0x0
+ field public static final int WRAP_CONTENT = -2; // 0xfffffffe
+ }
+
+ public static class BasicMeasure.Measure {
+ ctor public BasicMeasure.Measure();
+ field public static int SELF_DIMENSIONS;
+ field public static int TRY_GIVEN_DIMENSIONS;
+ field public static int USE_GIVEN_DIMENSIONS;
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! horizontalBehavior;
+ field public int horizontalDimension;
+ field public int measureStrategy;
+ field public int measuredBaseline;
+ field public boolean measuredHasBaseline;
+ field public int measuredHeight;
+ field public boolean measuredNeedsSolverPass;
+ field public int measuredWidth;
+ field public androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! verticalBehavior;
+ field public int verticalDimension;
+ }
+
+ public static interface BasicMeasure.Measurer {
+ method public void didMeasures();
+ method public void measure(androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measure!);
+ }
+
+ public class ChainRun extends androidx.constraintlayout.core.widgets.analyzer.WidgetRun {
+ ctor public ChainRun(androidx.constraintlayout.core.widgets.ConstraintWidget!, int);
+ method public void applyToWidget();
+ }
+
+ public interface Dependency {
+ method public void update(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ }
+
+ public class DependencyGraph {
+ ctor public DependencyGraph(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!);
+ method public void buildGraph();
+ method public void buildGraph(java.util.ArrayList<androidx.constraintlayout.core.widgets.analyzer.WidgetRun!>!);
+ method public void defineTerminalWidgets(androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!);
+ method public boolean directMeasure(boolean);
+ method public boolean directMeasureSetup(boolean);
+ method public boolean directMeasureWithOrientation(boolean, int);
+ method public void invalidateGraph();
+ method public void invalidateMeasures();
+ method public void measureWidgets();
+ method public void setMeasurer(androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer!);
+ }
+
+ public class DependencyNode implements androidx.constraintlayout.core.widgets.analyzer.Dependency {
+ ctor public DependencyNode(androidx.constraintlayout.core.widgets.analyzer.WidgetRun!);
+ method public void addDependency(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ method public void clear();
+ method public String! name();
+ method public void resolve(int);
+ method public void update(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ field public boolean delegateToWidgetRun;
+ field public boolean readyToSolve;
+ field public boolean resolved;
+ field public androidx.constraintlayout.core.widgets.analyzer.Dependency! updateDelegate;
+ field public int value;
+ }
+
+ public class Direct {
+ ctor public Direct();
+ method public static String! ls(int);
+ method public static boolean solveChain(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.LinearSystem!, int, int, androidx.constraintlayout.core.widgets.ChainHead!, boolean, boolean, boolean);
+ method public static void solvingPass(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer!);
+ }
+
+ public class Grouping {
+ ctor public Grouping();
+ method public static androidx.constraintlayout.core.widgets.analyzer.WidgetGroup! findDependents(androidx.constraintlayout.core.widgets.ConstraintWidget!, int, java.util.ArrayList<androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!>!, androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!);
+ method public static boolean simpleSolvingPass(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measurer!);
+ method public static boolean validInGroup(androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!, androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour!);
+ }
+
+ public class HorizontalWidgetRun extends androidx.constraintlayout.core.widgets.analyzer.WidgetRun {
+ ctor public HorizontalWidgetRun(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void applyToWidget();
+ }
+
+ public class VerticalWidgetRun extends androidx.constraintlayout.core.widgets.analyzer.WidgetRun {
+ ctor public VerticalWidgetRun(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void applyToWidget();
+ field public androidx.constraintlayout.core.widgets.analyzer.DependencyNode! baseline;
+ }
+
+ public class WidgetGroup {
+ ctor public WidgetGroup(int);
+ method public boolean add(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method public void apply();
+ method public void cleanup(java.util.ArrayList<androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!>!);
+ method public void clear();
+ method public int getId();
+ method public int getOrientation();
+ method public boolean intersectWith(androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!);
+ method public boolean isAuthoritative();
+ method public int measureWrap(androidx.constraintlayout.core.LinearSystem!, int);
+ method public void moveTo(int, androidx.constraintlayout.core.widgets.analyzer.WidgetGroup!);
+ method public void setAuthoritative(boolean);
+ method public void setOrientation(int);
+ method public int size();
+ }
+
+ public abstract class WidgetRun implements androidx.constraintlayout.core.widgets.analyzer.Dependency {
+ ctor public WidgetRun(androidx.constraintlayout.core.widgets.ConstraintWidget!);
+ method protected final void addTarget(androidx.constraintlayout.core.widgets.analyzer.DependencyNode!, androidx.constraintlayout.core.widgets.analyzer.DependencyNode!, int);
+ method protected final void addTarget(androidx.constraintlayout.core.widgets.analyzer.DependencyNode!, androidx.constraintlayout.core.widgets.analyzer.DependencyNode!, int, androidx.constraintlayout.core.widgets.analyzer.DimensionDependency!);
+ method protected final int getLimitedDimension(int, int);
+ method protected final androidx.constraintlayout.core.widgets.analyzer.DependencyNode! getTarget(androidx.constraintlayout.core.widgets.ConstraintAnchor!);
+ method protected final androidx.constraintlayout.core.widgets.analyzer.DependencyNode! getTarget(androidx.constraintlayout.core.widgets.ConstraintAnchor!, int);
+ method public long getWrapDimension();
+ method public boolean isCenterConnection();
+ method public boolean isDimensionResolved();
+ method public boolean isResolved();
+ method public void update(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ method protected void updateRunCenter(androidx.constraintlayout.core.widgets.analyzer.Dependency!, androidx.constraintlayout.core.widgets.ConstraintAnchor!, androidx.constraintlayout.core.widgets.ConstraintAnchor!, int);
+ method protected void updateRunEnd(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ method protected void updateRunStart(androidx.constraintlayout.core.widgets.analyzer.Dependency!);
+ method public long wrapSize(int);
+ field public androidx.constraintlayout.core.widgets.analyzer.DependencyNode! end;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour! mDimensionBehavior;
+ field protected androidx.constraintlayout.core.widgets.analyzer.WidgetRun.RunType! mRunType;
+ field public int matchConstraintsType;
+ field public int orientation;
+ field public androidx.constraintlayout.core.widgets.analyzer.DependencyNode! start;
+ }
+
+}
+
diff --git a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/utils/ArcCurveFit.java b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/utils/ArcCurveFit.java
index 7df5ef9..69e03f2 100644
--- a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/utils/ArcCurveFit.java
+++ b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/utils/ArcCurveFit.java
@@ -399,10 +399,12 @@
return mY1 + t * (mY2 - mY1);
}
+ @SuppressWarnings("UnusedVariable")
public double getLinearDX(double t) {
return mEllipseCenterX;
}
+ @SuppressWarnings("UnusedVariable")
public double getLinearDY(double t) {
return mEllipseCenterY;
}
diff --git a/constraintlayout/constraintlayout/api/2.2.0-beta01.txt b/constraintlayout/constraintlayout/api/2.2.0-beta01.txt
new file mode 100644
index 0000000..2d187a8
--- /dev/null
+++ b/constraintlayout/constraintlayout/api/2.2.0-beta01.txt
@@ -0,0 +1,1714 @@
+// Signature format: 4.0
+package androidx.constraintlayout.helper.widget {
+
+ public class Carousel extends androidx.constraintlayout.motion.widget.MotionHelper {
+ ctor public Carousel(android.content.Context!);
+ ctor public Carousel(android.content.Context!, android.util.AttributeSet!);
+ ctor public Carousel(android.content.Context!, android.util.AttributeSet!, int);
+ method public int getCount();
+ method public int getCurrentIndex();
+ method public boolean isInfinite();
+ method public void jumpToIndex(int);
+ method public void refresh();
+ method public void setAdapter(androidx.constraintlayout.helper.widget.Carousel.Adapter!);
+ method public void setInfinite(boolean);
+ method public void transitionToIndex(int, int);
+ field public static final int TOUCH_UP_CARRY_ON = 2; // 0x2
+ field public static final int TOUCH_UP_IMMEDIATE_STOP = 1; // 0x1
+ }
+
+ public static interface Carousel.Adapter {
+ method public int count();
+ method public void onNewItem(int);
+ method public void populate(android.view.View!, int);
+ }
+
+ public class CircularFlow extends androidx.constraintlayout.widget.VirtualLayout {
+ ctor public CircularFlow(android.content.Context!);
+ ctor public CircularFlow(android.content.Context!, android.util.AttributeSet!);
+ ctor public CircularFlow(android.content.Context!, android.util.AttributeSet!, int);
+ method public void addViewToCircularFlow(android.view.View!, int, float);
+ method public float[]! getAngles();
+ method public int[]! getRadius();
+ method public boolean isUpdatable(android.view.View!);
+ method public void setDefaultAngle(float);
+ method public void setDefaultRadius(int);
+ method public void updateAngle(android.view.View!, float);
+ method public void updateRadius(android.view.View!, int);
+ method public void updateReference(android.view.View!, int, float);
+ }
+
+ public class Flow extends androidx.constraintlayout.widget.VirtualLayout {
+ ctor public Flow(android.content.Context!);
+ ctor public Flow(android.content.Context!, android.util.AttributeSet!);
+ ctor public Flow(android.content.Context!, android.util.AttributeSet!, int);
+ method public void setFirstHorizontalBias(float);
+ method public void setFirstHorizontalStyle(int);
+ method public void setFirstVerticalBias(float);
+ method public void setFirstVerticalStyle(int);
+ method public void setHorizontalAlign(int);
+ method public void setHorizontalBias(float);
+ method public void setHorizontalGap(int);
+ method public void setHorizontalStyle(int);
+ method public void setLastHorizontalBias(float);
+ method public void setLastHorizontalStyle(int);
+ method public void setLastVerticalBias(float);
+ method public void setLastVerticalStyle(int);
+ method public void setMaxElementsWrap(int);
+ method public void setOrientation(int);
+ method public void setPadding(int);
+ method public void setPaddingBottom(int);
+ method public void setPaddingLeft(int);
+ method public void setPaddingRight(int);
+ method public void setPaddingTop(int);
+ method public void setVerticalAlign(int);
+ method public void setVerticalBias(float);
+ method public void setVerticalGap(int);
+ method public void setVerticalStyle(int);
+ method public void setWrapMode(int);
+ field public static final int CHAIN_PACKED = 2; // 0x2
+ field public static final int CHAIN_SPREAD = 0; // 0x0
+ field public static final int CHAIN_SPREAD_INSIDE = 1; // 0x1
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int HORIZONTAL_ALIGN_CENTER = 2; // 0x2
+ field public static final int HORIZONTAL_ALIGN_END = 1; // 0x1
+ field public static final int HORIZONTAL_ALIGN_START = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ field public static final int VERTICAL_ALIGN_BASELINE = 3; // 0x3
+ field public static final int VERTICAL_ALIGN_BOTTOM = 1; // 0x1
+ field public static final int VERTICAL_ALIGN_CENTER = 2; // 0x2
+ field public static final int VERTICAL_ALIGN_TOP = 0; // 0x0
+ field public static final int WRAP_ALIGNED = 2; // 0x2
+ field public static final int WRAP_CHAIN = 1; // 0x1
+ field public static final int WRAP_NONE = 0; // 0x0
+ }
+
+ public class Grid extends androidx.constraintlayout.widget.VirtualLayout {
+ ctor public Grid(android.content.Context!);
+ ctor public Grid(android.content.Context!, android.util.AttributeSet!);
+ ctor public Grid(android.content.Context!, android.util.AttributeSet!, int);
+ method public String! getColumnWeights();
+ method public int getColumns();
+ method public float getHorizontalGaps();
+ method public int getOrientation();
+ method public String! getRowWeights();
+ method public int getRows();
+ method public String! getSkips();
+ method public String! getSpans();
+ method public float getVerticalGaps();
+ method public void setColumnWeights(String!);
+ method public void setColumns(int);
+ method public void setHorizontalGaps(float);
+ method public void setOrientation(int);
+ method public void setRowWeights(String!);
+ method public void setRows(int);
+ method public void setSkips(String!);
+ method public void setSpans(CharSequence!);
+ method public void setVerticalGaps(float);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public class Layer extends androidx.constraintlayout.widget.ConstraintHelper {
+ ctor public Layer(android.content.Context!);
+ ctor public Layer(android.content.Context!, android.util.AttributeSet!);
+ ctor public Layer(android.content.Context!, android.util.AttributeSet!, int);
+ method protected void calcCenters();
+ field protected float mComputedCenterX;
+ field protected float mComputedCenterY;
+ field protected float mComputedMaxX;
+ field protected float mComputedMaxY;
+ field protected float mComputedMinX;
+ field protected float mComputedMinY;
+ }
+
+ public class MotionEffect extends androidx.constraintlayout.motion.widget.MotionHelper {
+ ctor public MotionEffect(android.content.Context!);
+ ctor public MotionEffect(android.content.Context!, android.util.AttributeSet!);
+ ctor public MotionEffect(android.content.Context!, android.util.AttributeSet!, int);
+ field public static final int AUTO = -1; // 0xffffffff
+ field public static final int EAST = 2; // 0x2
+ field public static final int NORTH = 0; // 0x0
+ field public static final int SOUTH = 1; // 0x1
+ field public static final String TAG = "FadeMove";
+ field public static final int WEST = 3; // 0x3
+ }
+
+ public class MotionPlaceholder extends androidx.constraintlayout.widget.VirtualLayout {
+ ctor public MotionPlaceholder(android.content.Context!);
+ ctor public MotionPlaceholder(android.content.Context!, android.util.AttributeSet!);
+ ctor public MotionPlaceholder(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public MotionPlaceholder(android.content.Context!, android.util.AttributeSet!, int, int);
+ }
+
+}
+
+package androidx.constraintlayout.motion.utils {
+
+ public class CustomSupport {
+ ctor public CustomSupport();
+ method public static void setInterpolatedValue(androidx.constraintlayout.widget.ConstraintAttribute!, android.view.View!, float[]!);
+ }
+
+ public class StopLogic extends androidx.constraintlayout.motion.widget.MotionInterpolator {
+ ctor public StopLogic();
+ method public void config(float, float, float, float, float, float);
+ method public String! debug(String!, float);
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ method public float getVelocity(float);
+ method public boolean isStopped();
+ method public void springConfig(float, float, float, float, float, float, float, int);
+ }
+
+ public abstract class ViewOscillator extends androidx.constraintlayout.core.motion.utils.KeyCycleOscillator {
+ ctor public ViewOscillator();
+ method public static androidx.constraintlayout.motion.utils.ViewOscillator! makeSpline(String!);
+ method public abstract void setProperty(android.view.View!, float);
+ }
+
+ public static class ViewOscillator.PathRotateSet extends androidx.constraintlayout.motion.utils.ViewOscillator {
+ ctor public ViewOscillator.PathRotateSet();
+ method public void setPathRotate(android.view.View!, float, double, double);
+ method public void setProperty(android.view.View!, float);
+ }
+
+ public abstract class ViewSpline extends androidx.constraintlayout.core.motion.utils.SplineSet {
+ ctor public ViewSpline();
+ method public static androidx.constraintlayout.motion.utils.ViewSpline! makeCustomSpline(String!, android.util.SparseArray<androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public static androidx.constraintlayout.motion.utils.ViewSpline! makeSpline(String!);
+ method public abstract void setProperty(android.view.View!, float);
+ }
+
+ public static class ViewSpline.CustomSet extends androidx.constraintlayout.motion.utils.ViewSpline {
+ ctor public ViewSpline.CustomSet(String!, android.util.SparseArray<androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public void setPoint(int, androidx.constraintlayout.widget.ConstraintAttribute!);
+ method public void setProperty(android.view.View!, float);
+ }
+
+ public static class ViewSpline.PathRotate extends androidx.constraintlayout.motion.utils.ViewSpline {
+ ctor public ViewSpline.PathRotate();
+ method public void setPathRotate(android.view.View!, float, double, double);
+ method public void setProperty(android.view.View!, float);
+ }
+
+ public class ViewState {
+ ctor public ViewState();
+ method public void getState(android.view.View!);
+ method public int height();
+ method public int width();
+ field public int bottom;
+ field public int left;
+ field public int right;
+ field public float rotation;
+ field public int top;
+ }
+
+ public abstract class ViewTimeCycle extends androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet {
+ ctor public ViewTimeCycle();
+ method public float get(float, long, android.view.View!, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ method public static androidx.constraintlayout.motion.utils.ViewTimeCycle! makeCustomSpline(String!, android.util.SparseArray<androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public static androidx.constraintlayout.motion.utils.ViewTimeCycle! makeSpline(String!, long);
+ method public abstract boolean setProperty(android.view.View!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ }
+
+ public static class ViewTimeCycle.CustomSet extends androidx.constraintlayout.motion.utils.ViewTimeCycle {
+ ctor public ViewTimeCycle.CustomSet(String!, android.util.SparseArray<androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public void setPoint(int, androidx.constraintlayout.widget.ConstraintAttribute!, float, int, float);
+ method public boolean setProperty(android.view.View!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ }
+
+ public static class ViewTimeCycle.PathRotate extends androidx.constraintlayout.motion.utils.ViewTimeCycle {
+ ctor public ViewTimeCycle.PathRotate();
+ method public boolean setPathRotate(android.view.View!, androidx.constraintlayout.core.motion.utils.KeyCache!, float, long, double, double);
+ method public boolean setProperty(android.view.View!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ }
+
+}
+
+package androidx.constraintlayout.motion.widget {
+
+ public interface Animatable {
+ method public float getProgress();
+ method public void setProgress(float);
+ }
+
+ public interface CustomFloatAttributes {
+ method public float get(String!);
+ method public String![]! getListOfAttributes();
+ method public void set(String!, float);
+ }
+
+ public class Debug {
+ ctor public Debug();
+ method public static void dumpLayoutParams(android.view.ViewGroup!, String!);
+ method public static void dumpLayoutParams(android.view.ViewGroup.LayoutParams!, String!);
+ method public static void dumpPoc(Object!);
+ method public static String! getActionType(android.view.MotionEvent!);
+ method public static String! getCallFrom(int);
+ method public static String! getLoc();
+ method public static String! getLocation();
+ method public static String! getLocation2();
+ method public static String! getName(android.content.Context!, int);
+ method public static String! getName(android.content.Context!, int[]!);
+ method public static String! getName(android.view.View!);
+ method public static String! getState(androidx.constraintlayout.motion.widget.MotionLayout!, int);
+ method public static String! getState(androidx.constraintlayout.motion.widget.MotionLayout!, int, int);
+ method public static void logStack(String!, String!, int);
+ method public static void printStack(String!, int);
+ }
+
+ public class DesignTool {
+ ctor public DesignTool(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public int designAccess(int, String!, Object!, float[]!, int, float[]!, int);
+ method public void disableAutoTransition(boolean);
+ method public void dumpConstraintSet(String!);
+ method public int getAnimationKeyFrames(Object!, float[]!);
+ method public int getAnimationPath(Object!, float[]!, int);
+ method public void getAnimationRectangles(Object!, float[]!);
+ method public String! getEndState();
+ method public int getKeyFrameInfo(Object!, int, int[]!);
+ method public float getKeyFramePosition(Object!, int, float, float);
+ method public int getKeyFramePositions(Object!, int[]!, float[]!);
+ method public Object! getKeyframe(int, int, int);
+ method public Object! getKeyframe(Object!, int, int);
+ method public Object! getKeyframeAtLocation(Object!, float, float);
+ method public Boolean! getPositionKeyframe(Object!, Object!, float, float, String![]!, float[]!);
+ method public float getProgress();
+ method public String! getStartState();
+ method public String! getState();
+ method public long getTransitionTimeMs();
+ method public boolean isInTransition();
+ method public void setAttributes(int, String!, Object!, Object!);
+ method public void setKeyFrame(Object!, int, String!, Object!);
+ method public boolean setKeyFramePosition(Object!, int, int, float, float);
+ method public void setKeyframe(Object!, String!, Object!);
+ method public void setState(String!);
+ method public void setToolPosition(float);
+ method public void setTransition(String!, String!);
+ method public void setViewDebug(Object!, int);
+ }
+
+ public interface FloatLayout {
+ method public void layout(float, float, float, float);
+ }
+
+ public abstract class Key {
+ ctor public Key();
+ method public abstract void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public abstract androidx.constraintlayout.motion.widget.Key! clone();
+ method public androidx.constraintlayout.motion.widget.Key! copy(androidx.constraintlayout.motion.widget.Key!);
+ method public int getFramePosition();
+ method public void setFramePosition(int);
+ method public void setInterpolation(java.util.HashMap<java.lang.String!,java.lang.Integer!>!);
+ method public abstract void setValue(String!, Object!);
+ method public androidx.constraintlayout.motion.widget.Key! setViewId(int);
+ field public static final String ALPHA = "alpha";
+ field public static final String CURVEFIT = "curveFit";
+ field public static final String CUSTOM = "CUSTOM";
+ field public static final String ELEVATION = "elevation";
+ field public static final String MOTIONPROGRESS = "motionProgress";
+ field public static final String PIVOT_X = "transformPivotX";
+ field public static final String PIVOT_Y = "transformPivotY";
+ field public static final String PROGRESS = "progress";
+ field public static final String ROTATION = "rotation";
+ field public static final String ROTATION_X = "rotationX";
+ field public static final String ROTATION_Y = "rotationY";
+ field public static final String SCALE_X = "scaleX";
+ field public static final String SCALE_Y = "scaleY";
+ field public static final String TRANSITIONEASING = "transitionEasing";
+ field public static final String TRANSITION_PATH_ROTATE = "transitionPathRotate";
+ field public static final String TRANSLATION_X = "translationX";
+ field public static final String TRANSLATION_Y = "translationY";
+ field public static final String TRANSLATION_Z = "translationZ";
+ field public static int UNSET;
+ field public static final String VISIBILITY = "visibility";
+ field public static final String WAVE_OFFSET = "waveOffset";
+ field public static final String WAVE_PERIOD = "wavePeriod";
+ field public static final String WAVE_PHASE = "wavePhase";
+ field public static final String WAVE_VARIES_BY = "waveVariesBy";
+ field protected int mType;
+ }
+
+ public class KeyAttributes extends androidx.constraintlayout.motion.widget.Key {
+ ctor public KeyAttributes();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public androidx.constraintlayout.motion.widget.Key! clone();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public void load(android.content.Context!, android.util.AttributeSet!);
+ method public void setValue(String!, Object!);
+ field public static final int KEY_TYPE = 1; // 0x1
+ }
+
+ public class KeyCycle extends androidx.constraintlayout.motion.widget.Key {
+ ctor public KeyCycle();
+ method public void addCycleValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewOscillator!>!);
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public androidx.constraintlayout.motion.widget.Key! clone();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public float getValue(String!);
+ method public void load(android.content.Context!, android.util.AttributeSet!);
+ method public void setValue(String!, Object!);
+ field public static final int KEY_TYPE = 4; // 0x4
+ field public static final int SHAPE_BOUNCE = 6; // 0x6
+ field public static final int SHAPE_COS_WAVE = 5; // 0x5
+ field public static final int SHAPE_REVERSE_SAW_WAVE = 4; // 0x4
+ field public static final int SHAPE_SAW_WAVE = 3; // 0x3
+ field public static final int SHAPE_SIN_WAVE = 0; // 0x0
+ field public static final int SHAPE_SQUARE_WAVE = 1; // 0x1
+ field public static final int SHAPE_TRIANGLE_WAVE = 2; // 0x2
+ field public static final String WAVE_OFFSET = "waveOffset";
+ field public static final String WAVE_PERIOD = "wavePeriod";
+ field public static final String WAVE_PHASE = "wavePhase";
+ field public static final String WAVE_SHAPE = "waveShape";
+ }
+
+ public class KeyFrames {
+ ctor public KeyFrames();
+ ctor public KeyFrames(android.content.Context!, org.xmlpull.v1.XmlPullParser!);
+ method public void addAllFrames(androidx.constraintlayout.motion.widget.MotionController!);
+ method public void addFrames(androidx.constraintlayout.motion.widget.MotionController!);
+ method public void addKey(androidx.constraintlayout.motion.widget.Key!);
+ method public java.util.ArrayList<androidx.constraintlayout.motion.widget.Key!>! getKeyFramesForView(int);
+ method public java.util.Set<java.lang.Integer!>! getKeys();
+ field public static final int UNSET = -1; // 0xffffffff
+ }
+
+ public class KeyPosition extends androidx.constraintlayout.motion.widget.Key {
+ ctor public KeyPosition();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public androidx.constraintlayout.motion.widget.Key! clone();
+ method public boolean intersects(int, int, android.graphics.RectF!, android.graphics.RectF!, float, float);
+ method public void load(android.content.Context!, android.util.AttributeSet!);
+ method public void positionAttributes(android.view.View!, android.graphics.RectF!, android.graphics.RectF!, float, float, String![]!, float[]!);
+ method public void setType(int);
+ method public void setValue(String!, Object!);
+ field public static final String DRAWPATH = "drawPath";
+ field public static final String PERCENT_HEIGHT = "percentHeight";
+ field public static final String PERCENT_WIDTH = "percentWidth";
+ field public static final String PERCENT_X = "percentX";
+ field public static final String PERCENT_Y = "percentY";
+ field public static final String SIZE_PERCENT = "sizePercent";
+ field public static final String TRANSITION_EASING = "transitionEasing";
+ field public static final int TYPE_AXIS = 3; // 0x3
+ field public static final int TYPE_CARTESIAN = 0; // 0x0
+ field public static final int TYPE_PATH = 1; // 0x1
+ field public static final int TYPE_SCREEN = 2; // 0x2
+ }
+
+ public class KeyTimeCycle extends androidx.constraintlayout.motion.widget.Key {
+ ctor public KeyTimeCycle();
+ method public void addTimeValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewTimeCycle!>!);
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public androidx.constraintlayout.motion.widget.Key! clone();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public void load(android.content.Context!, android.util.AttributeSet!);
+ method public void setValue(String!, Object!);
+ field public static final int KEY_TYPE = 3; // 0x3
+ field public static final int SHAPE_BOUNCE = 6; // 0x6
+ field public static final int SHAPE_COS_WAVE = 5; // 0x5
+ field public static final int SHAPE_REVERSE_SAW_WAVE = 4; // 0x4
+ field public static final int SHAPE_SAW_WAVE = 3; // 0x3
+ field public static final int SHAPE_SIN_WAVE = 0; // 0x0
+ field public static final int SHAPE_SQUARE_WAVE = 1; // 0x1
+ field public static final int SHAPE_TRIANGLE_WAVE = 2; // 0x2
+ field public static final String WAVE_OFFSET = "waveOffset";
+ field public static final String WAVE_PERIOD = "wavePeriod";
+ field public static final String WAVE_SHAPE = "waveShape";
+ }
+
+ public class KeyTrigger extends androidx.constraintlayout.motion.widget.Key {
+ ctor public KeyTrigger();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public androidx.constraintlayout.motion.widget.Key! clone();
+ method public void conditionallyFire(float, android.view.View!);
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public void load(android.content.Context!, android.util.AttributeSet!);
+ method public void setValue(String!, Object!);
+ field public static final String CROSS = "CROSS";
+ field public static final int KEY_TYPE = 5; // 0x5
+ field public static final String NEGATIVE_CROSS = "negativeCross";
+ field public static final String POSITIVE_CROSS = "positiveCross";
+ field public static final String POST_LAYOUT = "postLayout";
+ field public static final String TRIGGER_COLLISION_ID = "triggerCollisionId";
+ field public static final String TRIGGER_COLLISION_VIEW = "triggerCollisionView";
+ field public static final String TRIGGER_ID = "triggerID";
+ field public static final String TRIGGER_RECEIVER = "triggerReceiver";
+ field public static final String TRIGGER_SLACK = "triggerSlack";
+ field public static final String VIEW_TRANSITION_ON_CROSS = "viewTransitionOnCross";
+ field public static final String VIEW_TRANSITION_ON_NEGATIVE_CROSS = "viewTransitionOnNegativeCross";
+ field public static final String VIEW_TRANSITION_ON_POSITIVE_CROSS = "viewTransitionOnPositiveCross";
+ }
+
+ public class MotionController {
+ method public void addKey(androidx.constraintlayout.motion.widget.Key!);
+ method public int getAnimateRelativeTo();
+ method public void getCenter(double, float[]!, float[]!);
+ method public float getCenterX();
+ method public float getCenterY();
+ method public int getDrawPath();
+ method public float getFinalHeight();
+ method public float getFinalWidth();
+ method public float getFinalX();
+ method public float getFinalY();
+ method public int getKeyFrameInfo(int, int[]!);
+ method public int getKeyFramePositions(int[]!, float[]!);
+ method public float getStartHeight();
+ method public float getStartWidth();
+ method public float getStartX();
+ method public float getStartY();
+ method public int getTransformPivotTarget();
+ method public android.view.View! getView();
+ method public void remeasure();
+ method public void setDrawPath(int);
+ method public void setPathMotionArc(int);
+ method public void setStartState(androidx.constraintlayout.motion.utils.ViewState!, android.view.View!, int, int, int);
+ method public void setTransformPivotTarget(int);
+ method public void setView(android.view.View!);
+ method public void setup(int, int, float, long);
+ method public void setupRelative(androidx.constraintlayout.motion.widget.MotionController!);
+ field public static final int DRAW_PATH_AS_CONFIGURED = 4; // 0x4
+ field public static final int DRAW_PATH_BASIC = 1; // 0x1
+ field public static final int DRAW_PATH_CARTESIAN = 3; // 0x3
+ field public static final int DRAW_PATH_NONE = 0; // 0x0
+ field public static final int DRAW_PATH_RECTANGLE = 5; // 0x5
+ field public static final int DRAW_PATH_RELATIVE = 2; // 0x2
+ field public static final int DRAW_PATH_SCREEN = 6; // 0x6
+ field public static final int HORIZONTAL_PATH_X = 2; // 0x2
+ field public static final int HORIZONTAL_PATH_Y = 3; // 0x3
+ field public static final int PATH_PERCENT = 0; // 0x0
+ field public static final int PATH_PERPENDICULAR = 1; // 0x1
+ field public static final int ROTATION_LEFT = 2; // 0x2
+ field public static final int ROTATION_RIGHT = 1; // 0x1
+ field public static final int VERTICAL_PATH_X = 4; // 0x4
+ field public static final int VERTICAL_PATH_Y = 5; // 0x5
+ }
+
+ public class MotionHelper extends androidx.constraintlayout.widget.ConstraintHelper implements androidx.constraintlayout.motion.widget.MotionHelperInterface {
+ ctor public MotionHelper(android.content.Context!);
+ ctor public MotionHelper(android.content.Context!, android.util.AttributeSet!);
+ ctor public MotionHelper(android.content.Context!, android.util.AttributeSet!, int);
+ method public float getProgress();
+ method public boolean isDecorator();
+ method public boolean isUseOnHide();
+ method public boolean isUsedOnShow();
+ method public void onFinishedMotionScene(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public void onPostDraw(android.graphics.Canvas!);
+ method public void onPreDraw(android.graphics.Canvas!);
+ method public void onPreSetup(androidx.constraintlayout.motion.widget.MotionLayout!, java.util.HashMap<android.view.View!,androidx.constraintlayout.motion.widget.MotionController!>!);
+ method public void onTransitionChange(androidx.constraintlayout.motion.widget.MotionLayout!, int, int, float);
+ method public void onTransitionCompleted(androidx.constraintlayout.motion.widget.MotionLayout!, int);
+ method public void onTransitionStarted(androidx.constraintlayout.motion.widget.MotionLayout!, int, int);
+ method public void onTransitionTrigger(androidx.constraintlayout.motion.widget.MotionLayout!, int, boolean, float);
+ method public void setProgress(android.view.View!, float);
+ method public void setProgress(float);
+ field protected android.view.View![]! views;
+ }
+
+ public interface MotionHelperInterface extends androidx.constraintlayout.motion.widget.Animatable androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener {
+ method public boolean isDecorator();
+ method public boolean isUseOnHide();
+ method public boolean isUsedOnShow();
+ method public void onFinishedMotionScene(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public void onPostDraw(android.graphics.Canvas!);
+ method public void onPreDraw(android.graphics.Canvas!);
+ method public void onPreSetup(androidx.constraintlayout.motion.widget.MotionLayout!, java.util.HashMap<android.view.View!,androidx.constraintlayout.motion.widget.MotionController!>!);
+ }
+
+ public abstract class MotionInterpolator implements android.view.animation.Interpolator {
+ ctor public MotionInterpolator();
+ method public abstract float getVelocity();
+ }
+
+ public class MotionLayout extends androidx.constraintlayout.widget.ConstraintLayout implements androidx.core.view.NestedScrollingParent3 {
+ ctor public MotionLayout(android.content.Context);
+ ctor public MotionLayout(android.content.Context, android.util.AttributeSet?);
+ ctor public MotionLayout(android.content.Context, android.util.AttributeSet?, int);
+ method public void addTransitionListener(androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener!);
+ method public boolean applyViewTransition(int, androidx.constraintlayout.motion.widget.MotionController!);
+ method public androidx.constraintlayout.widget.ConstraintSet! cloneConstraintSet(int);
+ method public void enableTransition(int, boolean);
+ method public void enableViewTransition(int, boolean);
+ method protected void fireTransitionCompleted();
+ method public void fireTrigger(int, boolean, float);
+ method public androidx.constraintlayout.widget.ConstraintSet! getConstraintSet(int);
+ method @IdRes public int[]! getConstraintSetIds();
+ method public int getCurrentState();
+ method public java.util.ArrayList<androidx.constraintlayout.motion.widget.MotionScene.Transition!>! getDefinedTransitions();
+ method public androidx.constraintlayout.motion.widget.DesignTool! getDesignTool();
+ method public int getEndState();
+ method public int[]! getMatchingConstraintSetIds(java.lang.String!...!);
+ method protected long getNanoTime();
+ method public float getProgress();
+ method public androidx.constraintlayout.motion.widget.MotionScene! getScene();
+ method public int getStartState();
+ method public float getTargetPosition();
+ method public androidx.constraintlayout.motion.widget.MotionScene.Transition! getTransition(int);
+ method public android.os.Bundle! getTransitionState();
+ method public long getTransitionTimeMs();
+ method public float getVelocity();
+ method public void getViewVelocity(android.view.View!, float, float, float[]!, int);
+ method public boolean isDelayedApplicationOfInitialState();
+ method public boolean isInRotation();
+ method public boolean isInteractionEnabled();
+ method public boolean isViewTransitionEnabled(int);
+ method public void jumpToState(int);
+ method protected androidx.constraintlayout.motion.widget.MotionLayout.MotionTracker! obtainVelocityTracker();
+ method public void onNestedPreScroll(android.view.View, int, int, int[], int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int, int[]!);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
+ method public void onStopNestedScroll(android.view.View, int);
+ method @Deprecated public void rebuildMotion();
+ method public void rebuildScene();
+ method public boolean removeTransitionListener(androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener!);
+ method public void rotateTo(int, int);
+ method public void scheduleTransitionTo(int);
+ method public void setDebugMode(int);
+ method public void setDelayedApplicationOfInitialState(boolean);
+ method public void setInteractionEnabled(boolean);
+ method public void setInterpolatedProgress(float);
+ method public void setOnHide(float);
+ method public void setOnShow(float);
+ method public void setProgress(float);
+ method public void setProgress(float, float);
+ method public void setScene(androidx.constraintlayout.motion.widget.MotionScene!);
+ method protected void setTransition(androidx.constraintlayout.motion.widget.MotionScene.Transition!);
+ method public void setTransition(int);
+ method public void setTransition(int, int);
+ method public void setTransitionDuration(int);
+ method public void setTransitionListener(androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener!);
+ method public void setTransitionState(android.os.Bundle!);
+ method public void touchAnimateTo(int, float, float);
+ method public void touchSpringTo(float, float);
+ method public void transitionToEnd();
+ method public void transitionToEnd(Runnable!);
+ method public void transitionToStart();
+ method public void transitionToStart(Runnable!);
+ method public void transitionToState(int);
+ method public void transitionToState(int, int);
+ method public void transitionToState(int, int, int);
+ method public void transitionToState(int, int, int, int);
+ method public void updateState();
+ method public void updateState(int, androidx.constraintlayout.widget.ConstraintSet!);
+ method public void updateStateAnimate(int, androidx.constraintlayout.widget.ConstraintSet!, int);
+ method public void viewTransition(int, android.view.View!...!);
+ field public static final int DEBUG_SHOW_NONE = 0; // 0x0
+ field public static final int DEBUG_SHOW_PATH = 2; // 0x2
+ field public static final int DEBUG_SHOW_PROGRESS = 1; // 0x1
+ field public static boolean IS_IN_EDIT_MODE;
+ field public static final int TOUCH_UP_COMPLETE = 0; // 0x0
+ field public static final int TOUCH_UP_COMPLETE_TO_END = 2; // 0x2
+ field public static final int TOUCH_UP_COMPLETE_TO_START = 1; // 0x1
+ field public static final int TOUCH_UP_DECELERATE = 4; // 0x4
+ field public static final int TOUCH_UP_DECELERATE_AND_COMPLETE = 5; // 0x5
+ field public static final int TOUCH_UP_NEVER_TO_END = 7; // 0x7
+ field public static final int TOUCH_UP_NEVER_TO_START = 6; // 0x6
+ field public static final int TOUCH_UP_STOP = 3; // 0x3
+ field public static final int VELOCITY_LAYOUT = 1; // 0x1
+ field public static final int VELOCITY_POST_LAYOUT = 0; // 0x0
+ field public static final int VELOCITY_STATIC_LAYOUT = 3; // 0x3
+ field public static final int VELOCITY_STATIC_POST_LAYOUT = 2; // 0x2
+ field protected boolean mMeasureDuringTransition;
+ }
+
+ protected static interface MotionLayout.MotionTracker {
+ method public void addMovement(android.view.MotionEvent!);
+ method public void clear();
+ method public void computeCurrentVelocity(int);
+ method public void computeCurrentVelocity(int, float);
+ method public float getXVelocity();
+ method public float getXVelocity(int);
+ method public float getYVelocity();
+ method public float getYVelocity(int);
+ method public void recycle();
+ }
+
+ public static interface MotionLayout.TransitionListener {
+ method public void onTransitionChange(androidx.constraintlayout.motion.widget.MotionLayout!, int, int, float);
+ method public void onTransitionCompleted(androidx.constraintlayout.motion.widget.MotionLayout!, int);
+ method public void onTransitionStarted(androidx.constraintlayout.motion.widget.MotionLayout!, int, int);
+ method public void onTransitionTrigger(androidx.constraintlayout.motion.widget.MotionLayout!, int, boolean, float);
+ }
+
+ public class MotionScene {
+ ctor public MotionScene(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public void addOnClickListeners(androidx.constraintlayout.motion.widget.MotionLayout!, int);
+ method public void addTransition(androidx.constraintlayout.motion.widget.MotionScene.Transition!);
+ method public boolean applyViewTransition(int, androidx.constraintlayout.motion.widget.MotionController!);
+ method public androidx.constraintlayout.motion.widget.MotionScene.Transition! bestTransitionFor(int, float, float, android.view.MotionEvent!);
+ method public void disableAutoTransition(boolean);
+ method public void enableViewTransition(int, boolean);
+ method public int gatPathMotionArc();
+ method public androidx.constraintlayout.widget.ConstraintSet! getConstraintSet(android.content.Context!, String!);
+ method public int[]! getConstraintSetIds();
+ method public java.util.ArrayList<androidx.constraintlayout.motion.widget.MotionScene.Transition!>! getDefinedTransitions();
+ method public int getDuration();
+ method public android.view.animation.Interpolator! getInterpolator();
+ method public void getKeyFrames(androidx.constraintlayout.motion.widget.MotionController!);
+ method public int[]! getMatchingStateLabels(java.lang.String!...!);
+ method public float getPathPercent(android.view.View!, int);
+ method public float getStaggered();
+ method public androidx.constraintlayout.motion.widget.MotionScene.Transition! getTransitionById(int);
+ method public java.util.List<androidx.constraintlayout.motion.widget.MotionScene.Transition!>! getTransitionsWithState(int);
+ method public boolean isViewTransitionEnabled(int);
+ method public int lookUpConstraintId(String!);
+ method public String! lookUpConstraintName(int);
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void removeTransition(androidx.constraintlayout.motion.widget.MotionScene.Transition!);
+ method public void setConstraintSet(int, androidx.constraintlayout.widget.ConstraintSet!);
+ method public void setDuration(int);
+ method public void setKeyframe(android.view.View!, int, String!, Object!);
+ method public void setRtl(boolean);
+ method public void setTransition(androidx.constraintlayout.motion.widget.MotionScene.Transition!);
+ method public static String! stripID(String!);
+ method public boolean validateLayout(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public void viewTransition(int, android.view.View!...!);
+ field public static final int LAYOUT_CALL_MEASURE = 2; // 0x2
+ field public static final int LAYOUT_HONOR_REQUEST = 1; // 0x1
+ field public static final int LAYOUT_IGNORE_REQUEST = 0; // 0x0
+ field public static final int UNSET = -1; // 0xffffffff
+ }
+
+ public static class MotionScene.Transition {
+ ctor public MotionScene.Transition(int, androidx.constraintlayout.motion.widget.MotionScene!, int, int);
+ method public void addKeyFrame(androidx.constraintlayout.motion.widget.KeyFrames!);
+ method public void addOnClick(android.content.Context!, org.xmlpull.v1.XmlPullParser!);
+ method public void addOnClick(int, int);
+ method public String! debugString(android.content.Context!);
+ method public int getAutoTransition();
+ method public int getDuration();
+ method public int getEndConstraintSetId();
+ method public int getId();
+ method public java.util.List<androidx.constraintlayout.motion.widget.KeyFrames!>! getKeyFrameList();
+ method public int getLayoutDuringTransition();
+ method public java.util.List<androidx.constraintlayout.motion.widget.MotionScene.Transition.TransitionOnClick!>! getOnClickList();
+ method public int getPathMotionArc();
+ method public float getStagger();
+ method public int getStartConstraintSetId();
+ method public androidx.constraintlayout.motion.widget.TouchResponse! getTouchResponse();
+ method public boolean isEnabled();
+ method public boolean isTransitionFlag(int);
+ method public void removeOnClick(int);
+ method public void setAutoTransition(int);
+ method public void setDuration(int);
+ method public void setEnabled(boolean);
+ method public void setInterpolatorInfo(int, String!, int);
+ method public void setLayoutDuringTransition(int);
+ method public void setOnSwipe(androidx.constraintlayout.motion.widget.OnSwipe!);
+ method public void setOnTouchUp(int);
+ method public void setPathMotionArc(int);
+ method public void setStagger(float);
+ method public void setTransitionFlag(int);
+ field public static final int AUTO_ANIMATE_TO_END = 4; // 0x4
+ field public static final int AUTO_ANIMATE_TO_START = 3; // 0x3
+ field public static final int AUTO_JUMP_TO_END = 2; // 0x2
+ field public static final int AUTO_JUMP_TO_START = 1; // 0x1
+ field public static final int AUTO_NONE = 0; // 0x0
+ field public static final int INTERPOLATE_ANTICIPATE = 6; // 0x6
+ field public static final int INTERPOLATE_BOUNCE = 4; // 0x4
+ field public static final int INTERPOLATE_EASE_IN = 1; // 0x1
+ field public static final int INTERPOLATE_EASE_IN_OUT = 0; // 0x0
+ field public static final int INTERPOLATE_EASE_OUT = 2; // 0x2
+ field public static final int INTERPOLATE_LINEAR = 3; // 0x3
+ field public static final int INTERPOLATE_OVERSHOOT = 5; // 0x5
+ field public static final int INTERPOLATE_REFERENCE_ID = -2; // 0xfffffffe
+ field public static final int INTERPOLATE_SPLINE_STRING = -1; // 0xffffffff
+ }
+
+ public static class MotionScene.Transition.TransitionOnClick implements android.view.View.OnClickListener {
+ ctor public MotionScene.Transition.TransitionOnClick(android.content.Context!, androidx.constraintlayout.motion.widget.MotionScene.Transition!, org.xmlpull.v1.XmlPullParser!);
+ ctor public MotionScene.Transition.TransitionOnClick(androidx.constraintlayout.motion.widget.MotionScene.Transition!, int, int);
+ method public void addOnClickListeners(androidx.constraintlayout.motion.widget.MotionLayout!, int, androidx.constraintlayout.motion.widget.MotionScene.Transition!);
+ method public void onClick(android.view.View!);
+ method public void removeOnClickListeners(androidx.constraintlayout.motion.widget.MotionLayout!);
+ field public static final int ANIM_TOGGLE = 17; // 0x11
+ field public static final int ANIM_TO_END = 1; // 0x1
+ field public static final int ANIM_TO_START = 16; // 0x10
+ field public static final int JUMP_TO_END = 256; // 0x100
+ field public static final int JUMP_TO_START = 4096; // 0x1000
+ }
+
+ public class OnSwipe {
+ ctor public OnSwipe();
+ method public int getAutoCompleteMode();
+ method public int getDragDirection();
+ method public float getDragScale();
+ method public float getDragThreshold();
+ method public int getLimitBoundsTo();
+ method public float getMaxAcceleration();
+ method public float getMaxVelocity();
+ method public boolean getMoveWhenScrollAtTop();
+ method public int getNestedScrollFlags();
+ method public int getOnTouchUp();
+ method public int getRotationCenterId();
+ method public int getSpringBoundary();
+ method public float getSpringDamping();
+ method public float getSpringMass();
+ method public float getSpringStiffness();
+ method public float getSpringStopThreshold();
+ method public int getTouchAnchorId();
+ method public int getTouchAnchorSide();
+ method public int getTouchRegionId();
+ method public void setAutoCompleteMode(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setDragDirection(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setDragScale(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setDragThreshold(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setLimitBoundsTo(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setMaxAcceleration(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setMaxVelocity(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setMoveWhenScrollAtTop(boolean);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setNestedScrollFlags(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setOnTouchUp(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setRotateCenter(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setSpringBoundary(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setSpringDamping(float);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setSpringMass(float);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setSpringStiffness(float);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setSpringStopThreshold(float);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setTouchAnchorId(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setTouchAnchorSide(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setTouchRegionId(int);
+ field public static final int COMPLETE_MODE_CONTINUOUS_VELOCITY = 0; // 0x0
+ field public static final int COMPLETE_MODE_SPRING = 1; // 0x1
+ field public static final int DRAG_ANTICLOCKWISE = 7; // 0x7
+ field public static final int DRAG_CLOCKWISE = 6; // 0x6
+ field public static final int DRAG_DOWN = 1; // 0x1
+ field public static final int DRAG_END = 5; // 0x5
+ field public static final int DRAG_LEFT = 2; // 0x2
+ field public static final int DRAG_RIGHT = 3; // 0x3
+ field public static final int DRAG_START = 4; // 0x4
+ field public static final int DRAG_UP = 0; // 0x0
+ field public static final int FLAG_DISABLE_POST_SCROLL = 1; // 0x1
+ field public static final int FLAG_DISABLE_SCROLL = 2; // 0x2
+ field public static final int ON_UP_AUTOCOMPLETE = 0; // 0x0
+ field public static final int ON_UP_AUTOCOMPLETE_TO_END = 2; // 0x2
+ field public static final int ON_UP_AUTOCOMPLETE_TO_START = 1; // 0x1
+ field public static final int ON_UP_DECELERATE = 4; // 0x4
+ field public static final int ON_UP_DECELERATE_AND_COMPLETE = 5; // 0x5
+ field public static final int ON_UP_NEVER_TO_END = 7; // 0x7
+ field public static final int ON_UP_NEVER_TO_START = 6; // 0x6
+ field public static final int ON_UP_STOP = 3; // 0x3
+ field public static final int SIDE_BOTTOM = 3; // 0x3
+ field public static final int SIDE_END = 6; // 0x6
+ field public static final int SIDE_LEFT = 1; // 0x1
+ field public static final int SIDE_MIDDLE = 4; // 0x4
+ field public static final int SIDE_RIGHT = 2; // 0x2
+ field public static final int SIDE_START = 5; // 0x5
+ field public static final int SIDE_TOP = 0; // 0x0
+ field public static final int SPRING_BOUNDARY_BOUNCEBOTH = 3; // 0x3
+ field public static final int SPRING_BOUNDARY_BOUNCEEND = 2; // 0x2
+ field public static final int SPRING_BOUNDARY_BOUNCESTART = 1; // 0x1
+ field public static final int SPRING_BOUNDARY_OVERSHOOT = 0; // 0x0
+ }
+
+ public abstract class TransitionAdapter implements androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener {
+ ctor public TransitionAdapter();
+ method public void onTransitionChange(androidx.constraintlayout.motion.widget.MotionLayout!, int, int, float);
+ method public void onTransitionCompleted(androidx.constraintlayout.motion.widget.MotionLayout!, int);
+ method public void onTransitionStarted(androidx.constraintlayout.motion.widget.MotionLayout!, int, int);
+ method public void onTransitionTrigger(androidx.constraintlayout.motion.widget.MotionLayout!, int, boolean, float);
+ }
+
+ public class TransitionBuilder {
+ ctor public TransitionBuilder();
+ method public static androidx.constraintlayout.motion.widget.MotionScene.Transition! buildTransition(androidx.constraintlayout.motion.widget.MotionScene!, int, int, androidx.constraintlayout.widget.ConstraintSet!, int, androidx.constraintlayout.widget.ConstraintSet!);
+ method public static void validate(androidx.constraintlayout.motion.widget.MotionLayout!);
+ }
+
+ public class ViewTransition {
+ method public int getSharedValue();
+ method public int getSharedValueCurrent();
+ method public int getSharedValueID();
+ method public int getStateTransition();
+ method public void setSharedValue(int);
+ method public void setSharedValueCurrent(int);
+ method public void setSharedValueID(int);
+ method public void setStateTransition(int);
+ field public static final String CONSTRAINT_OVERRIDE = "ConstraintOverride";
+ field public static final String CUSTOM_ATTRIBUTE = "CustomAttribute";
+ field public static final String CUSTOM_METHOD = "CustomMethod";
+ field public static final String KEY_FRAME_SET_TAG = "KeyFrameSet";
+ field public static final int ONSTATE_ACTION_DOWN = 1; // 0x1
+ field public static final int ONSTATE_ACTION_DOWN_UP = 3; // 0x3
+ field public static final int ONSTATE_ACTION_UP = 2; // 0x2
+ field public static final int ONSTATE_SHARED_VALUE_SET = 4; // 0x4
+ field public static final int ONSTATE_SHARED_VALUE_UNSET = 5; // 0x5
+ field public static final String VIEW_TRANSITION_TAG = "ViewTransition";
+ }
+
+ public class ViewTransitionController {
+ ctor public ViewTransitionController(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public void add(androidx.constraintlayout.motion.widget.ViewTransition!);
+ }
+
+}
+
+package androidx.constraintlayout.utils.widget {
+
+ public class ImageFilterButton extends androidx.appcompat.widget.AppCompatImageButton {
+ ctor public ImageFilterButton(android.content.Context!);
+ ctor public ImageFilterButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public ImageFilterButton(android.content.Context!, android.util.AttributeSet!, int);
+ method public float getContrast();
+ method public float getCrossfade();
+ method public float getImagePanX();
+ method public float getImagePanY();
+ method public float getImageRotate();
+ method public float getImageZoom();
+ method public float getRound();
+ method public float getRoundPercent();
+ method public float getSaturation();
+ method public float getWarmth();
+ method public void setAltImageResource(int);
+ method public void setBrightness(float);
+ method public void setContrast(float);
+ method public void setCrossfade(float);
+ method public void setImagePanX(float);
+ method public void setImagePanY(float);
+ method public void setImageRotate(float);
+ method public void setImageZoom(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRound(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRoundPercent(float);
+ method public void setSaturation(float);
+ method public void setWarmth(float);
+ }
+
+ public class ImageFilterView extends androidx.appcompat.widget.AppCompatImageView {
+ ctor public ImageFilterView(android.content.Context!);
+ ctor public ImageFilterView(android.content.Context!, android.util.AttributeSet!);
+ ctor public ImageFilterView(android.content.Context!, android.util.AttributeSet!, int);
+ method public float getBrightness();
+ method public float getContrast();
+ method public float getCrossfade();
+ method public float getImagePanX();
+ method public float getImagePanY();
+ method public float getImageRotate();
+ method public float getImageZoom();
+ method public float getRound();
+ method public float getRoundPercent();
+ method public float getSaturation();
+ method public float getWarmth();
+ method public void setAltImageDrawable(android.graphics.drawable.Drawable!);
+ method public void setAltImageResource(int);
+ method public void setBrightness(float);
+ method public void setContrast(float);
+ method public void setCrossfade(float);
+ method public void setImagePanX(float);
+ method public void setImagePanY(float);
+ method public void setImageRotate(float);
+ method public void setImageZoom(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRound(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRoundPercent(float);
+ method public void setSaturation(float);
+ method public void setWarmth(float);
+ }
+
+ public class MockView extends android.view.View {
+ ctor public MockView(android.content.Context!);
+ ctor public MockView(android.content.Context!, android.util.AttributeSet!);
+ ctor public MockView(android.content.Context!, android.util.AttributeSet!, int);
+ method public void onDraw(android.graphics.Canvas);
+ field protected String! mText;
+ }
+
+ public class MotionButton extends androidx.appcompat.widget.AppCompatButton {
+ ctor public MotionButton(android.content.Context!);
+ ctor public MotionButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public MotionButton(android.content.Context!, android.util.AttributeSet!, int);
+ method public float getRound();
+ method public float getRoundPercent();
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRound(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRoundPercent(float);
+ }
+
+ public class MotionLabel extends android.view.View implements androidx.constraintlayout.motion.widget.FloatLayout {
+ ctor public MotionLabel(android.content.Context!);
+ ctor public MotionLabel(android.content.Context!, android.util.AttributeSet?);
+ ctor public MotionLabel(android.content.Context!, android.util.AttributeSet?, int);
+ method public float getRound();
+ method public float getRoundPercent();
+ method public float getScaleFromTextSize();
+ method public float getTextBackgroundPanX();
+ method public float getTextBackgroundPanY();
+ method public float getTextBackgroundRotate();
+ method public float getTextBackgroundZoom();
+ method public int getTextOutlineColor();
+ method public float getTextPanX();
+ method public float getTextPanY();
+ method public float getTextureHeight();
+ method public float getTextureWidth();
+ method public android.graphics.Typeface! getTypeface();
+ method public void layout(float, float, float, float);
+ method public void setGravity(int);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRound(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRoundPercent(float);
+ method public void setScaleFromTextSize(float);
+ method public void setText(CharSequence!);
+ method public void setTextBackgroundPanX(float);
+ method public void setTextBackgroundPanY(float);
+ method public void setTextBackgroundRotate(float);
+ method public void setTextBackgroundZoom(float);
+ method public void setTextFillColor(int);
+ method public void setTextOutlineColor(int);
+ method public void setTextOutlineThickness(float);
+ method public void setTextPanX(float);
+ method public void setTextPanY(float);
+ method public void setTextSize(float);
+ method public void setTextureHeight(float);
+ method public void setTextureWidth(float);
+ method public void setTypeface(android.graphics.Typeface!);
+ }
+
+ public class MotionTelltales extends androidx.constraintlayout.utils.widget.MockView {
+ ctor public MotionTelltales(android.content.Context!);
+ ctor public MotionTelltales(android.content.Context!, android.util.AttributeSet!);
+ ctor public MotionTelltales(android.content.Context!, android.util.AttributeSet!, int);
+ method public void setText(CharSequence!);
+ }
+
+}
+
+package androidx.constraintlayout.widget {
+
+ public class Barrier extends androidx.constraintlayout.widget.ConstraintHelper {
+ ctor public Barrier(android.content.Context!);
+ ctor public Barrier(android.content.Context!, android.util.AttributeSet!);
+ ctor public Barrier(android.content.Context!, android.util.AttributeSet!, int);
+ method @Deprecated public boolean allowsGoneWidget();
+ method public boolean getAllowsGoneWidget();
+ method public int getMargin();
+ method public int getType();
+ method public void setAllowsGoneWidget(boolean);
+ method public void setDpMargin(int);
+ method public void setMargin(int);
+ method public void setType(int);
+ field public static final int BOTTOM = 3; // 0x3
+ field public static final int END = 6; // 0x6
+ field public static final int LEFT = 0; // 0x0
+ field public static final int RIGHT = 1; // 0x1
+ field public static final int START = 5; // 0x5
+ field public static final int TOP = 2; // 0x2
+ }
+
+ public class ConstraintAttribute {
+ ctor public ConstraintAttribute(androidx.constraintlayout.widget.ConstraintAttribute!, Object!);
+ ctor public ConstraintAttribute(String!, androidx.constraintlayout.widget.ConstraintAttribute.AttributeType!);
+ ctor public ConstraintAttribute(String!, androidx.constraintlayout.widget.ConstraintAttribute.AttributeType!, Object!, boolean);
+ method public void applyCustom(android.view.View!);
+ method public boolean diff(androidx.constraintlayout.widget.ConstraintAttribute!);
+ method public static java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>! extractAttributes(java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>!, android.view.View!);
+ method public int getColorValue();
+ method public float getFloatValue();
+ method public int getIntegerValue();
+ method public String! getName();
+ method public String! getStringValue();
+ method public androidx.constraintlayout.widget.ConstraintAttribute.AttributeType! getType();
+ method public float getValueToInterpolate();
+ method public void getValuesToInterpolate(float[]!);
+ method public boolean isBooleanValue();
+ method public boolean isContinuous();
+ method public boolean isMethod();
+ method public int numberOfInterpolatedValues();
+ method public static void parse(android.content.Context!, org.xmlpull.v1.XmlPullParser!, java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public static void setAttributes(android.view.View!, java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public void setColorValue(int);
+ method public void setFloatValue(float);
+ method public void setIntValue(int);
+ method public void setStringValue(String!);
+ method public void setValue(float[]!);
+ method public void setValue(Object!);
+ }
+
+ public enum ConstraintAttribute.AttributeType {
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType BOOLEAN_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType COLOR_DRAWABLE_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType COLOR_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType DIMENSION_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType FLOAT_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType INT_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType REFERENCE_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType STRING_TYPE;
+ }
+
+ public abstract class ConstraintHelper extends android.view.View {
+ ctor public ConstraintHelper(android.content.Context!);
+ ctor public ConstraintHelper(android.content.Context!, android.util.AttributeSet!);
+ ctor public ConstraintHelper(android.content.Context!, android.util.AttributeSet!, int);
+ method public void addView(android.view.View!);
+ method public void applyHelperParams();
+ method protected void applyLayoutFeatures();
+ method protected void applyLayoutFeatures(androidx.constraintlayout.widget.ConstraintLayout!);
+ method protected void applyLayoutFeaturesInConstraintSet(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public boolean containsId(int);
+ method public int[]! getReferencedIds();
+ method protected android.view.View![]! getViews(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public int indexFromId(int);
+ method protected void init(android.util.AttributeSet!);
+ method public static boolean isChildOfHelper(android.view.View!);
+ method public void loadParameters(androidx.constraintlayout.widget.ConstraintSet.Constraint!, androidx.constraintlayout.core.widgets.HelperWidget!, androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!, android.util.SparseArray<androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public void onDraw(android.graphics.Canvas);
+ method public int removeView(android.view.View!);
+ method public void resolveRtl(androidx.constraintlayout.core.widgets.ConstraintWidget!, boolean);
+ method protected void setIds(String!);
+ method protected void setReferenceTags(String!);
+ method public void setReferencedIds(int[]!);
+ method public void updatePostConstraints(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void updatePostLayout(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void updatePostMeasure(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void updatePreDraw(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void updatePreLayout(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.widgets.Helper!, android.util.SparseArray<androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public void updatePreLayout(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void validateParams();
+ field protected static final String CHILD_TAG = "CONSTRAINT_LAYOUT_HELPER_CHILD";
+ field protected int mCount;
+ field protected androidx.constraintlayout.core.widgets.Helper! mHelperWidget;
+ field protected int[]! mIds;
+ field protected java.util.HashMap<java.lang.Integer!,java.lang.String!>! mMap;
+ field protected String! mReferenceIds;
+ field protected String! mReferenceTags;
+ field protected boolean mUseViewMeasure;
+ field protected android.content.Context! myContext;
+ }
+
+ public class ConstraintLayout extends android.view.ViewGroup {
+ ctor public ConstraintLayout(android.content.Context);
+ ctor public ConstraintLayout(android.content.Context, android.util.AttributeSet?);
+ ctor public ConstraintLayout(android.content.Context, android.util.AttributeSet?, int);
+ ctor public ConstraintLayout(android.content.Context, android.util.AttributeSet?, int, int);
+ method public void addValueModifier(androidx.constraintlayout.widget.ConstraintLayout.ValueModifier!);
+ method protected void applyConstraintsFromLayoutParams(boolean, android.view.View!, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!, android.util.SparseArray<androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method protected boolean dynamicUpdateConstraints(int, int);
+ method public void fillMetrics(androidx.constraintlayout.core.Metrics!);
+ method protected androidx.constraintlayout.widget.ConstraintLayout.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.constraintlayout.widget.ConstraintLayout.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
+ method public Object! getDesignInformation(int, Object!);
+ method public int getMaxHeight();
+ method public int getMaxWidth();
+ method public int getMinHeight();
+ method public int getMinWidth();
+ method public int getOptimizationLevel();
+ method public String! getSceneString();
+ method public static androidx.constraintlayout.widget.SharedValues! getSharedValues();
+ method public android.view.View! getViewById(int);
+ method public final androidx.constraintlayout.core.widgets.ConstraintWidget! getViewWidget(android.view.View!);
+ method protected boolean isRtl();
+ method public void loadLayoutDescription(int);
+ method protected void parseLayoutDescription(int);
+ method protected void resolveMeasuredDimension(int, int, int, int, boolean, boolean);
+ method protected void resolveSystem(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, int, int, int);
+ method public void setConstraintSet(androidx.constraintlayout.widget.ConstraintSet!);
+ method public void setDesignInformation(int, Object!, Object!);
+ method public void setMaxHeight(int);
+ method public void setMaxWidth(int);
+ method public void setMinHeight(int);
+ method public void setMinWidth(int);
+ method public void setOnConstraintsChanged(androidx.constraintlayout.widget.ConstraintsChangedListener!);
+ method public void setOptimizationLevel(int);
+ method protected void setSelfDimensionBehaviour(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, int, int, int, int);
+ method public void setState(int, int, int);
+ field public static final int DESIGN_INFO_ID = 0; // 0x0
+ field public static final String VERSION = "ConstraintLayout-2.2.0-alpha04";
+ field protected androidx.constraintlayout.widget.ConstraintLayoutStates! mConstraintLayoutSpec;
+ field protected boolean mDirtyHierarchy;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidgetContainer! mLayoutWidget;
+ }
+
+ public static class ConstraintLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public ConstraintLayout.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public ConstraintLayout.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public ConstraintLayout.LayoutParams(int, int);
+ method public String! getConstraintTag();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getConstraintWidget();
+ method public void reset();
+ method public void setWidgetDebugName(String!);
+ method public void validate();
+ field public static final int BASELINE = 5; // 0x5
+ field public static final int BOTTOM = 4; // 0x4
+ field public static final int CHAIN_PACKED = 2; // 0x2
+ field public static final int CHAIN_SPREAD = 0; // 0x0
+ field public static final int CHAIN_SPREAD_INSIDE = 1; // 0x1
+ field public static final int CIRCLE = 8; // 0x8
+ field public static final int END = 7; // 0x7
+ field public static final int GONE_UNSET = -2147483648; // 0x80000000
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int LEFT = 1; // 0x1
+ field public static final int MATCH_CONSTRAINT = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_PERCENT = 2; // 0x2
+ field public static final int MATCH_CONSTRAINT_SPREAD = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_WRAP = 1; // 0x1
+ field public static final int PARENT_ID = 0; // 0x0
+ field public static final int RIGHT = 2; // 0x2
+ field public static final int START = 6; // 0x6
+ field public static final int TOP = 3; // 0x3
+ field public static final int UNSET = -1; // 0xffffffff
+ field public static final int VERTICAL = 1; // 0x1
+ field public static final int WRAP_BEHAVIOR_HORIZONTAL_ONLY = 1; // 0x1
+ field public static final int WRAP_BEHAVIOR_INCLUDED = 0; // 0x0
+ field public static final int WRAP_BEHAVIOR_SKIPPED = 3; // 0x3
+ field public static final int WRAP_BEHAVIOR_VERTICAL_ONLY = 2; // 0x2
+ field public int baselineMargin;
+ field public int baselineToBaseline;
+ field public int baselineToBottom;
+ field public int baselineToTop;
+ field public int bottomToBottom;
+ field public int bottomToTop;
+ field public float circleAngle;
+ field public int circleConstraint;
+ field public int circleRadius;
+ field public boolean constrainedHeight;
+ field public boolean constrainedWidth;
+ field public String! constraintTag;
+ field public String! dimensionRatio;
+ field public int editorAbsoluteX;
+ field public int editorAbsoluteY;
+ field public int endToEnd;
+ field public int endToStart;
+ field public int goneBaselineMargin;
+ field public int goneBottomMargin;
+ field public int goneEndMargin;
+ field public int goneLeftMargin;
+ field public int goneRightMargin;
+ field public int goneStartMargin;
+ field public int goneTopMargin;
+ field public int guideBegin;
+ field public int guideEnd;
+ field public float guidePercent;
+ field public boolean guidelineUseRtl;
+ field public boolean helped;
+ field public float horizontalBias;
+ field public int horizontalChainStyle;
+ field public float horizontalWeight;
+ field public int leftToLeft;
+ field public int leftToRight;
+ field public int matchConstraintDefaultHeight;
+ field public int matchConstraintDefaultWidth;
+ field public int matchConstraintMaxHeight;
+ field public int matchConstraintMaxWidth;
+ field public int matchConstraintMinHeight;
+ field public int matchConstraintMinWidth;
+ field public float matchConstraintPercentHeight;
+ field public float matchConstraintPercentWidth;
+ field public int orientation;
+ field public int rightToLeft;
+ field public int rightToRight;
+ field public int startToEnd;
+ field public int startToStart;
+ field public int topToBottom;
+ field public int topToTop;
+ field public float verticalBias;
+ field public int verticalChainStyle;
+ field public float verticalWeight;
+ field public int wrapBehaviorInParent;
+ }
+
+ public static interface ConstraintLayout.ValueModifier {
+ method public boolean update(int, int, int, android.view.View!, androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!);
+ }
+
+ public class ConstraintLayoutStates {
+ method public boolean needsToChange(int, float, float);
+ method public void setOnConstraintsChanged(androidx.constraintlayout.widget.ConstraintsChangedListener!);
+ method public void updateConstraints(int, float, float);
+ field public static final String TAG = "ConstraintLayoutStates";
+ }
+
+ public class ConstraintLayoutStatistics {
+ ctor public ConstraintLayoutStatistics(androidx.constraintlayout.widget.ConstraintLayout!);
+ ctor public ConstraintLayoutStatistics(androidx.constraintlayout.widget.ConstraintLayoutStatistics!);
+ method public void attach(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public androidx.constraintlayout.widget.ConstraintLayoutStatistics! clone();
+ method public void detach();
+ method public long getValue(int);
+ method public void logSummary(String!);
+ method public void logSummary(String!, androidx.constraintlayout.widget.ConstraintLayoutStatistics!);
+ method public void reset();
+ field public static final int DURATION_OF_CHILD_MEASURES = 5; // 0x5
+ field public static final int DURATION_OF_LAYOUT = 7; // 0x7
+ field public static final int DURATION_OF_MEASURES = 6; // 0x6
+ field public static final int NUMBER_OF_CHILD_MEASURES = 4; // 0x4
+ field public static final int NUMBER_OF_CHILD_VIEWS = 3; // 0x3
+ field public static final int NUMBER_OF_EQUATIONS = 9; // 0x9
+ field public static final int NUMBER_OF_LAYOUTS = 1; // 0x1
+ field public static final int NUMBER_OF_ON_MEASURES = 2; // 0x2
+ field public static final int NUMBER_OF_SIMPLE_EQUATIONS = 10; // 0xa
+ field public static final int NUMBER_OF_VARIABLES = 8; // 0x8
+ }
+
+ public class ConstraintProperties {
+ ctor public ConstraintProperties(android.view.View!);
+ method public androidx.constraintlayout.widget.ConstraintProperties! addToHorizontalChain(int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! addToHorizontalChainRTL(int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! addToVerticalChain(int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! alpha(float);
+ method public void apply();
+ method public androidx.constraintlayout.widget.ConstraintProperties! center(int, int, int, int, int, int, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerHorizontally(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerHorizontally(int, int, int, int, int, int, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerHorizontallyRtl(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerHorizontallyRtl(int, int, int, int, int, int, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerVertically(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerVertically(int, int, int, int, int, int, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! connect(int, int, int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainDefaultHeight(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainDefaultWidth(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainHeight(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainMaxHeight(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainMaxWidth(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainMinHeight(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainMinWidth(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainWidth(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! dimensionRatio(String!);
+ method public androidx.constraintlayout.widget.ConstraintProperties! elevation(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! goneMargin(int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! horizontalBias(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! horizontalChainStyle(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! horizontalWeight(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! margin(int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! removeConstraints(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! removeFromHorizontalChain();
+ method public androidx.constraintlayout.widget.ConstraintProperties! removeFromVerticalChain();
+ method public androidx.constraintlayout.widget.ConstraintProperties! rotation(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! rotationX(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! rotationY(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! scaleX(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! scaleY(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! transformPivot(float, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! transformPivotX(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! transformPivotY(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! translation(float, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! translationX(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! translationY(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! translationZ(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! verticalBias(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! verticalChainStyle(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! verticalWeight(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! visibility(int);
+ field public static final int BASELINE = 5; // 0x5
+ field public static final int BOTTOM = 4; // 0x4
+ field public static final int END = 7; // 0x7
+ field public static final int LEFT = 1; // 0x1
+ field public static final int MATCH_CONSTRAINT = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_SPREAD = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_WRAP = 1; // 0x1
+ field public static final int PARENT_ID = 0; // 0x0
+ field public static final int RIGHT = 2; // 0x2
+ field public static final int START = 6; // 0x6
+ field public static final int TOP = 3; // 0x3
+ field public static final int UNSET = -1; // 0xffffffff
+ field public static final int WRAP_CONTENT = -2; // 0xfffffffe
+ }
+
+ public class ConstraintSet {
+ ctor public ConstraintSet();
+ method public void addColorAttributes(java.lang.String!...!);
+ method public void addFloatAttributes(java.lang.String!...!);
+ method public void addIntAttributes(java.lang.String!...!);
+ method public void addStringAttributes(java.lang.String!...!);
+ method public void addToHorizontalChain(int, int, int);
+ method public void addToHorizontalChainRTL(int, int, int);
+ method public void addToVerticalChain(int, int, int);
+ method public void applyCustomAttributes(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void applyDeltaFrom(androidx.constraintlayout.widget.ConstraintSet!);
+ method public void applyTo(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void applyToHelper(androidx.constraintlayout.widget.ConstraintHelper!, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!, android.util.SparseArray<androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public void applyToLayoutParams(int, androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!);
+ method public void applyToWithoutCustom(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public static androidx.constraintlayout.widget.ConstraintSet.Constraint! buildDelta(android.content.Context!, org.xmlpull.v1.XmlPullParser!);
+ method public void center(int, int, int, int, int, int, int, float);
+ method public void centerHorizontally(int, int);
+ method public void centerHorizontally(int, int, int, int, int, int, int, float);
+ method public void centerHorizontallyRtl(int, int);
+ method public void centerHorizontallyRtl(int, int, int, int, int, int, int, float);
+ method public void centerVertically(int, int);
+ method public void centerVertically(int, int, int, int, int, int, int, float);
+ method public void clear(int);
+ method public void clear(int, int);
+ method public void clone(android.content.Context!, int);
+ method public void clone(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void clone(androidx.constraintlayout.widget.Constraints!);
+ method public void clone(androidx.constraintlayout.widget.ConstraintSet!);
+ method public void connect(int, int, int, int);
+ method public void connect(int, int, int, int, int);
+ method public void constrainCircle(int, int, int, float);
+ method public void constrainDefaultHeight(int, int);
+ method public void constrainDefaultWidth(int, int);
+ method public void constrainHeight(int, int);
+ method public void constrainMaxHeight(int, int);
+ method public void constrainMaxWidth(int, int);
+ method public void constrainMinHeight(int, int);
+ method public void constrainMinWidth(int, int);
+ method public void constrainPercentHeight(int, float);
+ method public void constrainPercentWidth(int, float);
+ method public void constrainWidth(int, int);
+ method public void constrainedHeight(int, boolean);
+ method public void constrainedWidth(int, boolean);
+ method public void create(int, int);
+ method public void createBarrier(int, int, int, int...!);
+ method public void createHorizontalChain(int, int, int, int, int[]!, float[]!, int);
+ method public void createHorizontalChainRtl(int, int, int, int, int[]!, float[]!, int);
+ method public void createVerticalChain(int, int, int, int, int[]!, float[]!, int);
+ method public void dump(androidx.constraintlayout.motion.widget.MotionScene!, int...!);
+ method public boolean getApplyElevation(int);
+ method public androidx.constraintlayout.widget.ConstraintSet.Constraint! getConstraint(int);
+ method public java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>! getCustomAttributeSet();
+ method public int getHeight(int);
+ method public int[]! getKnownIds();
+ method public androidx.constraintlayout.widget.ConstraintSet.Constraint! getParameters(int);
+ method public int[]! getReferencedIds(int);
+ method public String![]! getStateLabels();
+ method public int getVisibility(int);
+ method public int getVisibilityMode(int);
+ method public int getWidth(int);
+ method public boolean isForceId();
+ method public boolean isValidateOnParse();
+ method public void load(android.content.Context!, int);
+ method public void load(android.content.Context!, org.xmlpull.v1.XmlPullParser!);
+ method public boolean matchesLabels(java.lang.String!...!);
+ method public void parseColorAttributes(androidx.constraintlayout.widget.ConstraintSet.Constraint!, String!);
+ method public void parseFloatAttributes(androidx.constraintlayout.widget.ConstraintSet.Constraint!, String!);
+ method public void parseIntAttributes(androidx.constraintlayout.widget.ConstraintSet.Constraint!, String!);
+ method public void parseStringAttributes(androidx.constraintlayout.widget.ConstraintSet.Constraint!, String!);
+ method public void readFallback(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void readFallback(androidx.constraintlayout.widget.ConstraintSet!);
+ method public void removeAttribute(String!);
+ method public void removeFromHorizontalChain(int);
+ method public void removeFromVerticalChain(int);
+ method public void setAlpha(int, float);
+ method public void setApplyElevation(int, boolean);
+ method public void setBarrierType(int, int);
+ method public void setColorValue(int, String!, int);
+ method public void setDimensionRatio(int, String!);
+ method public void setEditorAbsoluteX(int, int);
+ method public void setEditorAbsoluteY(int, int);
+ method public void setElevation(int, float);
+ method public void setFloatValue(int, String!, float);
+ method public void setForceId(boolean);
+ method public void setGoneMargin(int, int, int);
+ method public void setGuidelineBegin(int, int);
+ method public void setGuidelineEnd(int, int);
+ method public void setGuidelinePercent(int, float);
+ method public void setHorizontalBias(int, float);
+ method public void setHorizontalChainStyle(int, int);
+ method public void setHorizontalWeight(int, float);
+ method public void setIntValue(int, String!, int);
+ method public void setLayoutWrapBehavior(int, int);
+ method public void setMargin(int, int, int);
+ method public void setReferencedIds(int, int...!);
+ method public void setRotation(int, float);
+ method public void setRotationX(int, float);
+ method public void setRotationY(int, float);
+ method public void setScaleX(int, float);
+ method public void setScaleY(int, float);
+ method public void setStateLabels(String!);
+ method public void setStateLabelsList(java.lang.String!...!);
+ method public void setStringValue(int, String!, String!);
+ method public void setTransformPivot(int, float, float);
+ method public void setTransformPivotX(int, float);
+ method public void setTransformPivotY(int, float);
+ method public void setTranslation(int, float, float);
+ method public void setTranslationX(int, float);
+ method public void setTranslationY(int, float);
+ method public void setTranslationZ(int, float);
+ method public void setValidateOnParse(boolean);
+ method public void setVerticalBias(int, float);
+ method public void setVerticalChainStyle(int, int);
+ method public void setVerticalWeight(int, float);
+ method public void setVisibility(int, int);
+ method public void setVisibilityMode(int, int);
+ method public void writeState(java.io.Writer!, androidx.constraintlayout.widget.ConstraintLayout!, int) throws java.io.IOException;
+ field public static final int BASELINE = 5; // 0x5
+ field public static final int BOTTOM = 4; // 0x4
+ field public static final int CHAIN_PACKED = 2; // 0x2
+ field public static final int CHAIN_SPREAD = 0; // 0x0
+ field public static final int CHAIN_SPREAD_INSIDE = 1; // 0x1
+ field public static final int CIRCLE_REFERENCE = 8; // 0x8
+ field public static final int END = 7; // 0x7
+ field public static final int GONE = 8; // 0x8
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int HORIZONTAL_GUIDELINE = 0; // 0x0
+ field public static final int INVISIBLE = 4; // 0x4
+ field public static final int LEFT = 1; // 0x1
+ field public static final int MATCH_CONSTRAINT = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_PERCENT = 2; // 0x2
+ field public static final int MATCH_CONSTRAINT_SPREAD = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_WRAP = 1; // 0x1
+ field public static final int PARENT_ID = 0; // 0x0
+ field public static final int RIGHT = 2; // 0x2
+ field public static final int ROTATE_LEFT_OF_PORTRATE = 4; // 0x4
+ field public static final int ROTATE_NONE = 0; // 0x0
+ field public static final int ROTATE_PORTRATE_OF_LEFT = 2; // 0x2
+ field public static final int ROTATE_PORTRATE_OF_RIGHT = 1; // 0x1
+ field public static final int ROTATE_RIGHT_OF_PORTRATE = 3; // 0x3
+ field public static final int START = 6; // 0x6
+ field public static final int TOP = 3; // 0x3
+ field public static final int UNSET = -1; // 0xffffffff
+ field public static final int VERTICAL = 1; // 0x1
+ field public static final int VERTICAL_GUIDELINE = 1; // 0x1
+ field public static final int VISIBILITY_MODE_IGNORE = 1; // 0x1
+ field public static final int VISIBILITY_MODE_NORMAL = 0; // 0x0
+ field public static final int VISIBLE = 0; // 0x0
+ field public static final int WRAP_CONTENT = -2; // 0xfffffffe
+ field public String! derivedState;
+ field public String! mIdString;
+ field public int mRotate;
+ }
+
+ public static class ConstraintSet.Constraint {
+ ctor public ConstraintSet.Constraint();
+ method public void applyDelta(androidx.constraintlayout.widget.ConstraintSet.Constraint!);
+ method public void applyTo(androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!);
+ method public androidx.constraintlayout.widget.ConstraintSet.Constraint! clone();
+ method public void printDelta(String!);
+ field public final androidx.constraintlayout.widget.ConstraintSet.Layout! layout;
+ field public java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>! mCustomConstraints;
+ field public final androidx.constraintlayout.widget.ConstraintSet.Motion! motion;
+ field public final androidx.constraintlayout.widget.ConstraintSet.PropertySet! propertySet;
+ field public final androidx.constraintlayout.widget.ConstraintSet.Transform! transform;
+ }
+
+ public static class ConstraintSet.Layout {
+ ctor public ConstraintSet.Layout();
+ method public void copyFrom(androidx.constraintlayout.widget.ConstraintSet.Layout!);
+ method public void dump(androidx.constraintlayout.motion.widget.MotionScene!, StringBuilder!);
+ field public static final int UNSET = -1; // 0xffffffff
+ field public static final int UNSET_GONE_MARGIN = -2147483648; // 0x80000000
+ field public int baselineMargin;
+ field public int baselineToBaseline;
+ field public int baselineToBottom;
+ field public int baselineToTop;
+ field public int bottomMargin;
+ field public int bottomToBottom;
+ field public int bottomToTop;
+ field public float circleAngle;
+ field public int circleConstraint;
+ field public int circleRadius;
+ field public boolean constrainedHeight;
+ field public boolean constrainedWidth;
+ field public String! dimensionRatio;
+ field public int editorAbsoluteX;
+ field public int editorAbsoluteY;
+ field public int endMargin;
+ field public int endToEnd;
+ field public int endToStart;
+ field public int goneBaselineMargin;
+ field public int goneBottomMargin;
+ field public int goneEndMargin;
+ field public int goneLeftMargin;
+ field public int goneRightMargin;
+ field public int goneStartMargin;
+ field public int goneTopMargin;
+ field public int guideBegin;
+ field public int guideEnd;
+ field public float guidePercent;
+ field public boolean guidelineUseRtl;
+ field public int heightDefault;
+ field public int heightMax;
+ field public int heightMin;
+ field public float heightPercent;
+ field public float horizontalBias;
+ field public int horizontalChainStyle;
+ field public float horizontalWeight;
+ field public int leftMargin;
+ field public int leftToLeft;
+ field public int leftToRight;
+ field public boolean mApply;
+ field public boolean mBarrierAllowsGoneWidgets;
+ field public int mBarrierDirection;
+ field public int mBarrierMargin;
+ field public String! mConstraintTag;
+ field public int mHeight;
+ field public int mHelperType;
+ field public boolean mIsGuideline;
+ field public boolean mOverride;
+ field public String! mReferenceIdString;
+ field public int[]! mReferenceIds;
+ field public int mWidth;
+ field public int mWrapBehavior;
+ field public int orientation;
+ field public int rightMargin;
+ field public int rightToLeft;
+ field public int rightToRight;
+ field public int startMargin;
+ field public int startToEnd;
+ field public int startToStart;
+ field public int topMargin;
+ field public int topToBottom;
+ field public int topToTop;
+ field public float verticalBias;
+ field public int verticalChainStyle;
+ field public float verticalWeight;
+ field public int widthDefault;
+ field public int widthMax;
+ field public int widthMin;
+ field public float widthPercent;
+ }
+
+ public static class ConstraintSet.Motion {
+ ctor public ConstraintSet.Motion();
+ method public void copyFrom(androidx.constraintlayout.widget.ConstraintSet.Motion!);
+ field public int mAnimateCircleAngleTo;
+ field public int mAnimateRelativeTo;
+ field public boolean mApply;
+ field public int mDrawPath;
+ field public float mMotionStagger;
+ field public int mPathMotionArc;
+ field public float mPathRotate;
+ field public int mPolarRelativeTo;
+ field public int mQuantizeInterpolatorID;
+ field public String! mQuantizeInterpolatorString;
+ field public int mQuantizeInterpolatorType;
+ field public float mQuantizeMotionPhase;
+ field public int mQuantizeMotionSteps;
+ field public String! mTransitionEasing;
+ }
+
+ public static class ConstraintSet.PropertySet {
+ ctor public ConstraintSet.PropertySet();
+ method public void copyFrom(androidx.constraintlayout.widget.ConstraintSet.PropertySet!);
+ field public float alpha;
+ field public boolean mApply;
+ field public float mProgress;
+ field public int mVisibilityMode;
+ field public int visibility;
+ }
+
+ public static class ConstraintSet.Transform {
+ ctor public ConstraintSet.Transform();
+ method public void copyFrom(androidx.constraintlayout.widget.ConstraintSet.Transform!);
+ field public boolean applyElevation;
+ field public float elevation;
+ field public boolean mApply;
+ field public float rotation;
+ field public float rotationX;
+ field public float rotationY;
+ field public float scaleX;
+ field public float scaleY;
+ field public int transformPivotTarget;
+ field public float transformPivotX;
+ field public float transformPivotY;
+ field public float translationX;
+ field public float translationY;
+ field public float translationZ;
+ }
+
+ public class Constraints extends android.view.ViewGroup {
+ ctor public Constraints(android.content.Context!);
+ ctor public Constraints(android.content.Context!, android.util.AttributeSet!);
+ ctor public Constraints(android.content.Context!, android.util.AttributeSet!, int);
+ method protected androidx.constraintlayout.widget.Constraints.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.constraintlayout.widget.Constraints.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
+ method public androidx.constraintlayout.widget.ConstraintSet! getConstraintSet();
+ field public static final String TAG = "Constraints";
+ }
+
+ public static class Constraints.LayoutParams extends androidx.constraintlayout.widget.ConstraintLayout.LayoutParams {
+ ctor public Constraints.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public Constraints.LayoutParams(androidx.constraintlayout.widget.Constraints.LayoutParams!);
+ ctor public Constraints.LayoutParams(int, int);
+ field public float alpha;
+ field public boolean applyElevation;
+ field public float elevation;
+ field public float rotation;
+ field public float rotationX;
+ field public float rotationY;
+ field public float scaleX;
+ field public float scaleY;
+ field public float transformPivotX;
+ field public float transformPivotY;
+ field public float translationX;
+ field public float translationY;
+ field public float translationZ;
+ }
+
+ public abstract class ConstraintsChangedListener {
+ ctor public ConstraintsChangedListener();
+ method public void postLayoutChange(int, int);
+ method public void preLayoutChange(int, int);
+ }
+
+ public class Group extends androidx.constraintlayout.widget.ConstraintHelper {
+ ctor public Group(android.content.Context!);
+ ctor public Group(android.content.Context!, android.util.AttributeSet!);
+ ctor public Group(android.content.Context!, android.util.AttributeSet!, int);
+ method public void onAttachedToWindow();
+ }
+
+ public class Guideline extends android.view.View {
+ ctor public Guideline(android.content.Context!);
+ ctor public Guideline(android.content.Context!, android.util.AttributeSet!);
+ ctor public Guideline(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public Guideline(android.content.Context!, android.util.AttributeSet!, int, int);
+ method public void setFilterRedundantCalls(boolean);
+ method public void setGuidelineBegin(int);
+ method public void setGuidelineEnd(int);
+ method public void setGuidelinePercent(float);
+ }
+
+ public class Placeholder extends android.view.View {
+ ctor public Placeholder(android.content.Context!);
+ ctor public Placeholder(android.content.Context!, android.util.AttributeSet!);
+ ctor public Placeholder(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public Placeholder(android.content.Context!, android.util.AttributeSet!, int, int);
+ method public android.view.View! getContent();
+ method public int getEmptyVisibility();
+ method public void onDraw(android.graphics.Canvas);
+ method public void setContentId(int);
+ method public void setEmptyVisibility(int);
+ method public void updatePostMeasure(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void updatePreLayout(androidx.constraintlayout.widget.ConstraintLayout!);
+ }
+
+ public class ReactiveGuide extends android.view.View implements androidx.constraintlayout.widget.SharedValues.SharedValuesListener {
+ ctor public ReactiveGuide(android.content.Context!);
+ ctor public ReactiveGuide(android.content.Context!, android.util.AttributeSet!);
+ ctor public ReactiveGuide(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public ReactiveGuide(android.content.Context!, android.util.AttributeSet!, int, int);
+ method public int getApplyToConstraintSetId();
+ method public int getAttributeId();
+ method public boolean isAnimatingChange();
+ method public void onNewValue(int, int, int);
+ method public void setAnimateChange(boolean);
+ method public void setApplyToConstraintSetId(int);
+ method public void setAttributeId(int);
+ method public void setGuidelineBegin(int);
+ method public void setGuidelineEnd(int);
+ method public void setGuidelinePercent(float);
+ }
+
+ public class SharedValues {
+ ctor public SharedValues();
+ method public void addListener(int, androidx.constraintlayout.widget.SharedValues.SharedValuesListener!);
+ method public void clearListeners();
+ method public void fireNewValue(int, int);
+ method public int getValue(int);
+ method public void removeListener(androidx.constraintlayout.widget.SharedValues.SharedValuesListener!);
+ method public void removeListener(int, androidx.constraintlayout.widget.SharedValues.SharedValuesListener!);
+ field public static final int UNSET = -1; // 0xffffffff
+ }
+
+ public static interface SharedValues.SharedValuesListener {
+ method public void onNewValue(int, int, int);
+ }
+
+ public class StateSet {
+ ctor public StateSet(android.content.Context!, org.xmlpull.v1.XmlPullParser!);
+ method public int convertToConstraintSet(int, int, float, float);
+ method public boolean needsToChange(int, float, float);
+ method public void setOnConstraintsChanged(androidx.constraintlayout.widget.ConstraintsChangedListener!);
+ method public int stateGetConstraintID(int, int, int);
+ method public int updateConstraints(int, int, float, float);
+ field public static final String TAG = "ConstraintLayoutStates";
+ }
+
+ public abstract class VirtualLayout extends androidx.constraintlayout.widget.ConstraintHelper {
+ ctor public VirtualLayout(android.content.Context!);
+ ctor public VirtualLayout(android.content.Context!, android.util.AttributeSet!);
+ ctor public VirtualLayout(android.content.Context!, android.util.AttributeSet!, int);
+ method public void onAttachedToWindow();
+ method public void onMeasure(androidx.constraintlayout.core.widgets.VirtualLayout!, int, int);
+ }
+
+}
+
diff --git a/activity/activity-compose/api/res-1.10.0-beta01.txt b/constraintlayout/constraintlayout/api/res-2.2.0-beta01.txt
similarity index 100%
copy from activity/activity-compose/api/res-1.10.0-beta01.txt
copy to constraintlayout/constraintlayout/api/res-2.2.0-beta01.txt
diff --git a/constraintlayout/constraintlayout/api/restricted_2.2.0-beta01.txt b/constraintlayout/constraintlayout/api/restricted_2.2.0-beta01.txt
new file mode 100644
index 0000000..2d187a8
--- /dev/null
+++ b/constraintlayout/constraintlayout/api/restricted_2.2.0-beta01.txt
@@ -0,0 +1,1714 @@
+// Signature format: 4.0
+package androidx.constraintlayout.helper.widget {
+
+ public class Carousel extends androidx.constraintlayout.motion.widget.MotionHelper {
+ ctor public Carousel(android.content.Context!);
+ ctor public Carousel(android.content.Context!, android.util.AttributeSet!);
+ ctor public Carousel(android.content.Context!, android.util.AttributeSet!, int);
+ method public int getCount();
+ method public int getCurrentIndex();
+ method public boolean isInfinite();
+ method public void jumpToIndex(int);
+ method public void refresh();
+ method public void setAdapter(androidx.constraintlayout.helper.widget.Carousel.Adapter!);
+ method public void setInfinite(boolean);
+ method public void transitionToIndex(int, int);
+ field public static final int TOUCH_UP_CARRY_ON = 2; // 0x2
+ field public static final int TOUCH_UP_IMMEDIATE_STOP = 1; // 0x1
+ }
+
+ public static interface Carousel.Adapter {
+ method public int count();
+ method public void onNewItem(int);
+ method public void populate(android.view.View!, int);
+ }
+
+ public class CircularFlow extends androidx.constraintlayout.widget.VirtualLayout {
+ ctor public CircularFlow(android.content.Context!);
+ ctor public CircularFlow(android.content.Context!, android.util.AttributeSet!);
+ ctor public CircularFlow(android.content.Context!, android.util.AttributeSet!, int);
+ method public void addViewToCircularFlow(android.view.View!, int, float);
+ method public float[]! getAngles();
+ method public int[]! getRadius();
+ method public boolean isUpdatable(android.view.View!);
+ method public void setDefaultAngle(float);
+ method public void setDefaultRadius(int);
+ method public void updateAngle(android.view.View!, float);
+ method public void updateRadius(android.view.View!, int);
+ method public void updateReference(android.view.View!, int, float);
+ }
+
+ public class Flow extends androidx.constraintlayout.widget.VirtualLayout {
+ ctor public Flow(android.content.Context!);
+ ctor public Flow(android.content.Context!, android.util.AttributeSet!);
+ ctor public Flow(android.content.Context!, android.util.AttributeSet!, int);
+ method public void setFirstHorizontalBias(float);
+ method public void setFirstHorizontalStyle(int);
+ method public void setFirstVerticalBias(float);
+ method public void setFirstVerticalStyle(int);
+ method public void setHorizontalAlign(int);
+ method public void setHorizontalBias(float);
+ method public void setHorizontalGap(int);
+ method public void setHorizontalStyle(int);
+ method public void setLastHorizontalBias(float);
+ method public void setLastHorizontalStyle(int);
+ method public void setLastVerticalBias(float);
+ method public void setLastVerticalStyle(int);
+ method public void setMaxElementsWrap(int);
+ method public void setOrientation(int);
+ method public void setPadding(int);
+ method public void setPaddingBottom(int);
+ method public void setPaddingLeft(int);
+ method public void setPaddingRight(int);
+ method public void setPaddingTop(int);
+ method public void setVerticalAlign(int);
+ method public void setVerticalBias(float);
+ method public void setVerticalGap(int);
+ method public void setVerticalStyle(int);
+ method public void setWrapMode(int);
+ field public static final int CHAIN_PACKED = 2; // 0x2
+ field public static final int CHAIN_SPREAD = 0; // 0x0
+ field public static final int CHAIN_SPREAD_INSIDE = 1; // 0x1
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int HORIZONTAL_ALIGN_CENTER = 2; // 0x2
+ field public static final int HORIZONTAL_ALIGN_END = 1; // 0x1
+ field public static final int HORIZONTAL_ALIGN_START = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ field public static final int VERTICAL_ALIGN_BASELINE = 3; // 0x3
+ field public static final int VERTICAL_ALIGN_BOTTOM = 1; // 0x1
+ field public static final int VERTICAL_ALIGN_CENTER = 2; // 0x2
+ field public static final int VERTICAL_ALIGN_TOP = 0; // 0x0
+ field public static final int WRAP_ALIGNED = 2; // 0x2
+ field public static final int WRAP_CHAIN = 1; // 0x1
+ field public static final int WRAP_NONE = 0; // 0x0
+ }
+
+ public class Grid extends androidx.constraintlayout.widget.VirtualLayout {
+ ctor public Grid(android.content.Context!);
+ ctor public Grid(android.content.Context!, android.util.AttributeSet!);
+ ctor public Grid(android.content.Context!, android.util.AttributeSet!, int);
+ method public String! getColumnWeights();
+ method public int getColumns();
+ method public float getHorizontalGaps();
+ method public int getOrientation();
+ method public String! getRowWeights();
+ method public int getRows();
+ method public String! getSkips();
+ method public String! getSpans();
+ method public float getVerticalGaps();
+ method public void setColumnWeights(String!);
+ method public void setColumns(int);
+ method public void setHorizontalGaps(float);
+ method public void setOrientation(int);
+ method public void setRowWeights(String!);
+ method public void setRows(int);
+ method public void setSkips(String!);
+ method public void setSpans(CharSequence!);
+ method public void setVerticalGaps(float);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public class Layer extends androidx.constraintlayout.widget.ConstraintHelper {
+ ctor public Layer(android.content.Context!);
+ ctor public Layer(android.content.Context!, android.util.AttributeSet!);
+ ctor public Layer(android.content.Context!, android.util.AttributeSet!, int);
+ method protected void calcCenters();
+ field protected float mComputedCenterX;
+ field protected float mComputedCenterY;
+ field protected float mComputedMaxX;
+ field protected float mComputedMaxY;
+ field protected float mComputedMinX;
+ field protected float mComputedMinY;
+ }
+
+ public class MotionEffect extends androidx.constraintlayout.motion.widget.MotionHelper {
+ ctor public MotionEffect(android.content.Context!);
+ ctor public MotionEffect(android.content.Context!, android.util.AttributeSet!);
+ ctor public MotionEffect(android.content.Context!, android.util.AttributeSet!, int);
+ field public static final int AUTO = -1; // 0xffffffff
+ field public static final int EAST = 2; // 0x2
+ field public static final int NORTH = 0; // 0x0
+ field public static final int SOUTH = 1; // 0x1
+ field public static final String TAG = "FadeMove";
+ field public static final int WEST = 3; // 0x3
+ }
+
+ public class MotionPlaceholder extends androidx.constraintlayout.widget.VirtualLayout {
+ ctor public MotionPlaceholder(android.content.Context!);
+ ctor public MotionPlaceholder(android.content.Context!, android.util.AttributeSet!);
+ ctor public MotionPlaceholder(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public MotionPlaceholder(android.content.Context!, android.util.AttributeSet!, int, int);
+ }
+
+}
+
+package androidx.constraintlayout.motion.utils {
+
+ public class CustomSupport {
+ ctor public CustomSupport();
+ method public static void setInterpolatedValue(androidx.constraintlayout.widget.ConstraintAttribute!, android.view.View!, float[]!);
+ }
+
+ public class StopLogic extends androidx.constraintlayout.motion.widget.MotionInterpolator {
+ ctor public StopLogic();
+ method public void config(float, float, float, float, float, float);
+ method public String! debug(String!, float);
+ method public float getInterpolation(float);
+ method public float getVelocity();
+ method public float getVelocity(float);
+ method public boolean isStopped();
+ method public void springConfig(float, float, float, float, float, float, float, int);
+ }
+
+ public abstract class ViewOscillator extends androidx.constraintlayout.core.motion.utils.KeyCycleOscillator {
+ ctor public ViewOscillator();
+ method public static androidx.constraintlayout.motion.utils.ViewOscillator! makeSpline(String!);
+ method public abstract void setProperty(android.view.View!, float);
+ }
+
+ public static class ViewOscillator.PathRotateSet extends androidx.constraintlayout.motion.utils.ViewOscillator {
+ ctor public ViewOscillator.PathRotateSet();
+ method public void setPathRotate(android.view.View!, float, double, double);
+ method public void setProperty(android.view.View!, float);
+ }
+
+ public abstract class ViewSpline extends androidx.constraintlayout.core.motion.utils.SplineSet {
+ ctor public ViewSpline();
+ method public static androidx.constraintlayout.motion.utils.ViewSpline! makeCustomSpline(String!, android.util.SparseArray<androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public static androidx.constraintlayout.motion.utils.ViewSpline! makeSpline(String!);
+ method public abstract void setProperty(android.view.View!, float);
+ }
+
+ public static class ViewSpline.CustomSet extends androidx.constraintlayout.motion.utils.ViewSpline {
+ ctor public ViewSpline.CustomSet(String!, android.util.SparseArray<androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public void setPoint(int, androidx.constraintlayout.widget.ConstraintAttribute!);
+ method public void setProperty(android.view.View!, float);
+ }
+
+ public static class ViewSpline.PathRotate extends androidx.constraintlayout.motion.utils.ViewSpline {
+ ctor public ViewSpline.PathRotate();
+ method public void setPathRotate(android.view.View!, float, double, double);
+ method public void setProperty(android.view.View!, float);
+ }
+
+ public class ViewState {
+ ctor public ViewState();
+ method public void getState(android.view.View!);
+ method public int height();
+ method public int width();
+ field public int bottom;
+ field public int left;
+ field public int right;
+ field public float rotation;
+ field public int top;
+ }
+
+ public abstract class ViewTimeCycle extends androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet {
+ ctor public ViewTimeCycle();
+ method public float get(float, long, android.view.View!, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ method public static androidx.constraintlayout.motion.utils.ViewTimeCycle! makeCustomSpline(String!, android.util.SparseArray<androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public static androidx.constraintlayout.motion.utils.ViewTimeCycle! makeSpline(String!, long);
+ method public abstract boolean setProperty(android.view.View!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ }
+
+ public static class ViewTimeCycle.CustomSet extends androidx.constraintlayout.motion.utils.ViewTimeCycle {
+ ctor public ViewTimeCycle.CustomSet(String!, android.util.SparseArray<androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public void setPoint(int, androidx.constraintlayout.widget.ConstraintAttribute!, float, int, float);
+ method public boolean setProperty(android.view.View!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ }
+
+ public static class ViewTimeCycle.PathRotate extends androidx.constraintlayout.motion.utils.ViewTimeCycle {
+ ctor public ViewTimeCycle.PathRotate();
+ method public boolean setPathRotate(android.view.View!, androidx.constraintlayout.core.motion.utils.KeyCache!, float, long, double, double);
+ method public boolean setProperty(android.view.View!, float, long, androidx.constraintlayout.core.motion.utils.KeyCache!);
+ }
+
+}
+
+package androidx.constraintlayout.motion.widget {
+
+ public interface Animatable {
+ method public float getProgress();
+ method public void setProgress(float);
+ }
+
+ public interface CustomFloatAttributes {
+ method public float get(String!);
+ method public String![]! getListOfAttributes();
+ method public void set(String!, float);
+ }
+
+ public class Debug {
+ ctor public Debug();
+ method public static void dumpLayoutParams(android.view.ViewGroup!, String!);
+ method public static void dumpLayoutParams(android.view.ViewGroup.LayoutParams!, String!);
+ method public static void dumpPoc(Object!);
+ method public static String! getActionType(android.view.MotionEvent!);
+ method public static String! getCallFrom(int);
+ method public static String! getLoc();
+ method public static String! getLocation();
+ method public static String! getLocation2();
+ method public static String! getName(android.content.Context!, int);
+ method public static String! getName(android.content.Context!, int[]!);
+ method public static String! getName(android.view.View!);
+ method public static String! getState(androidx.constraintlayout.motion.widget.MotionLayout!, int);
+ method public static String! getState(androidx.constraintlayout.motion.widget.MotionLayout!, int, int);
+ method public static void logStack(String!, String!, int);
+ method public static void printStack(String!, int);
+ }
+
+ public class DesignTool {
+ ctor public DesignTool(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public int designAccess(int, String!, Object!, float[]!, int, float[]!, int);
+ method public void disableAutoTransition(boolean);
+ method public void dumpConstraintSet(String!);
+ method public int getAnimationKeyFrames(Object!, float[]!);
+ method public int getAnimationPath(Object!, float[]!, int);
+ method public void getAnimationRectangles(Object!, float[]!);
+ method public String! getEndState();
+ method public int getKeyFrameInfo(Object!, int, int[]!);
+ method public float getKeyFramePosition(Object!, int, float, float);
+ method public int getKeyFramePositions(Object!, int[]!, float[]!);
+ method public Object! getKeyframe(int, int, int);
+ method public Object! getKeyframe(Object!, int, int);
+ method public Object! getKeyframeAtLocation(Object!, float, float);
+ method public Boolean! getPositionKeyframe(Object!, Object!, float, float, String![]!, float[]!);
+ method public float getProgress();
+ method public String! getStartState();
+ method public String! getState();
+ method public long getTransitionTimeMs();
+ method public boolean isInTransition();
+ method public void setAttributes(int, String!, Object!, Object!);
+ method public void setKeyFrame(Object!, int, String!, Object!);
+ method public boolean setKeyFramePosition(Object!, int, int, float, float);
+ method public void setKeyframe(Object!, String!, Object!);
+ method public void setState(String!);
+ method public void setToolPosition(float);
+ method public void setTransition(String!, String!);
+ method public void setViewDebug(Object!, int);
+ }
+
+ public interface FloatLayout {
+ method public void layout(float, float, float, float);
+ }
+
+ public abstract class Key {
+ ctor public Key();
+ method public abstract void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public abstract androidx.constraintlayout.motion.widget.Key! clone();
+ method public androidx.constraintlayout.motion.widget.Key! copy(androidx.constraintlayout.motion.widget.Key!);
+ method public int getFramePosition();
+ method public void setFramePosition(int);
+ method public void setInterpolation(java.util.HashMap<java.lang.String!,java.lang.Integer!>!);
+ method public abstract void setValue(String!, Object!);
+ method public androidx.constraintlayout.motion.widget.Key! setViewId(int);
+ field public static final String ALPHA = "alpha";
+ field public static final String CURVEFIT = "curveFit";
+ field public static final String CUSTOM = "CUSTOM";
+ field public static final String ELEVATION = "elevation";
+ field public static final String MOTIONPROGRESS = "motionProgress";
+ field public static final String PIVOT_X = "transformPivotX";
+ field public static final String PIVOT_Y = "transformPivotY";
+ field public static final String PROGRESS = "progress";
+ field public static final String ROTATION = "rotation";
+ field public static final String ROTATION_X = "rotationX";
+ field public static final String ROTATION_Y = "rotationY";
+ field public static final String SCALE_X = "scaleX";
+ field public static final String SCALE_Y = "scaleY";
+ field public static final String TRANSITIONEASING = "transitionEasing";
+ field public static final String TRANSITION_PATH_ROTATE = "transitionPathRotate";
+ field public static final String TRANSLATION_X = "translationX";
+ field public static final String TRANSLATION_Y = "translationY";
+ field public static final String TRANSLATION_Z = "translationZ";
+ field public static int UNSET;
+ field public static final String VISIBILITY = "visibility";
+ field public static final String WAVE_OFFSET = "waveOffset";
+ field public static final String WAVE_PERIOD = "wavePeriod";
+ field public static final String WAVE_PHASE = "wavePhase";
+ field public static final String WAVE_VARIES_BY = "waveVariesBy";
+ field protected int mType;
+ }
+
+ public class KeyAttributes extends androidx.constraintlayout.motion.widget.Key {
+ ctor public KeyAttributes();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public androidx.constraintlayout.motion.widget.Key! clone();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public void load(android.content.Context!, android.util.AttributeSet!);
+ method public void setValue(String!, Object!);
+ field public static final int KEY_TYPE = 1; // 0x1
+ }
+
+ public class KeyCycle extends androidx.constraintlayout.motion.widget.Key {
+ ctor public KeyCycle();
+ method public void addCycleValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewOscillator!>!);
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public androidx.constraintlayout.motion.widget.Key! clone();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public float getValue(String!);
+ method public void load(android.content.Context!, android.util.AttributeSet!);
+ method public void setValue(String!, Object!);
+ field public static final int KEY_TYPE = 4; // 0x4
+ field public static final int SHAPE_BOUNCE = 6; // 0x6
+ field public static final int SHAPE_COS_WAVE = 5; // 0x5
+ field public static final int SHAPE_REVERSE_SAW_WAVE = 4; // 0x4
+ field public static final int SHAPE_SAW_WAVE = 3; // 0x3
+ field public static final int SHAPE_SIN_WAVE = 0; // 0x0
+ field public static final int SHAPE_SQUARE_WAVE = 1; // 0x1
+ field public static final int SHAPE_TRIANGLE_WAVE = 2; // 0x2
+ field public static final String WAVE_OFFSET = "waveOffset";
+ field public static final String WAVE_PERIOD = "wavePeriod";
+ field public static final String WAVE_PHASE = "wavePhase";
+ field public static final String WAVE_SHAPE = "waveShape";
+ }
+
+ public class KeyFrames {
+ ctor public KeyFrames();
+ ctor public KeyFrames(android.content.Context!, org.xmlpull.v1.XmlPullParser!);
+ method public void addAllFrames(androidx.constraintlayout.motion.widget.MotionController!);
+ method public void addFrames(androidx.constraintlayout.motion.widget.MotionController!);
+ method public void addKey(androidx.constraintlayout.motion.widget.Key!);
+ method public java.util.ArrayList<androidx.constraintlayout.motion.widget.Key!>! getKeyFramesForView(int);
+ method public java.util.Set<java.lang.Integer!>! getKeys();
+ field public static final int UNSET = -1; // 0xffffffff
+ }
+
+ public class KeyPosition extends androidx.constraintlayout.motion.widget.Key {
+ ctor public KeyPosition();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public androidx.constraintlayout.motion.widget.Key! clone();
+ method public boolean intersects(int, int, android.graphics.RectF!, android.graphics.RectF!, float, float);
+ method public void load(android.content.Context!, android.util.AttributeSet!);
+ method public void positionAttributes(android.view.View!, android.graphics.RectF!, android.graphics.RectF!, float, float, String![]!, float[]!);
+ method public void setType(int);
+ method public void setValue(String!, Object!);
+ field public static final String DRAWPATH = "drawPath";
+ field public static final String PERCENT_HEIGHT = "percentHeight";
+ field public static final String PERCENT_WIDTH = "percentWidth";
+ field public static final String PERCENT_X = "percentX";
+ field public static final String PERCENT_Y = "percentY";
+ field public static final String SIZE_PERCENT = "sizePercent";
+ field public static final String TRANSITION_EASING = "transitionEasing";
+ field public static final int TYPE_AXIS = 3; // 0x3
+ field public static final int TYPE_CARTESIAN = 0; // 0x0
+ field public static final int TYPE_PATH = 1; // 0x1
+ field public static final int TYPE_SCREEN = 2; // 0x2
+ }
+
+ public class KeyTimeCycle extends androidx.constraintlayout.motion.widget.Key {
+ ctor public KeyTimeCycle();
+ method public void addTimeValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewTimeCycle!>!);
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public androidx.constraintlayout.motion.widget.Key! clone();
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public void load(android.content.Context!, android.util.AttributeSet!);
+ method public void setValue(String!, Object!);
+ field public static final int KEY_TYPE = 3; // 0x3
+ field public static final int SHAPE_BOUNCE = 6; // 0x6
+ field public static final int SHAPE_COS_WAVE = 5; // 0x5
+ field public static final int SHAPE_REVERSE_SAW_WAVE = 4; // 0x4
+ field public static final int SHAPE_SAW_WAVE = 3; // 0x3
+ field public static final int SHAPE_SIN_WAVE = 0; // 0x0
+ field public static final int SHAPE_SQUARE_WAVE = 1; // 0x1
+ field public static final int SHAPE_TRIANGLE_WAVE = 2; // 0x2
+ field public static final String WAVE_OFFSET = "waveOffset";
+ field public static final String WAVE_PERIOD = "wavePeriod";
+ field public static final String WAVE_SHAPE = "waveShape";
+ }
+
+ public class KeyTrigger extends androidx.constraintlayout.motion.widget.Key {
+ ctor public KeyTrigger();
+ method public void addValues(java.util.HashMap<java.lang.String!,androidx.constraintlayout.motion.utils.ViewSpline!>!);
+ method public androidx.constraintlayout.motion.widget.Key! clone();
+ method public void conditionallyFire(float, android.view.View!);
+ method public void getAttributeNames(java.util.HashSet<java.lang.String!>!);
+ method public void load(android.content.Context!, android.util.AttributeSet!);
+ method public void setValue(String!, Object!);
+ field public static final String CROSS = "CROSS";
+ field public static final int KEY_TYPE = 5; // 0x5
+ field public static final String NEGATIVE_CROSS = "negativeCross";
+ field public static final String POSITIVE_CROSS = "positiveCross";
+ field public static final String POST_LAYOUT = "postLayout";
+ field public static final String TRIGGER_COLLISION_ID = "triggerCollisionId";
+ field public static final String TRIGGER_COLLISION_VIEW = "triggerCollisionView";
+ field public static final String TRIGGER_ID = "triggerID";
+ field public static final String TRIGGER_RECEIVER = "triggerReceiver";
+ field public static final String TRIGGER_SLACK = "triggerSlack";
+ field public static final String VIEW_TRANSITION_ON_CROSS = "viewTransitionOnCross";
+ field public static final String VIEW_TRANSITION_ON_NEGATIVE_CROSS = "viewTransitionOnNegativeCross";
+ field public static final String VIEW_TRANSITION_ON_POSITIVE_CROSS = "viewTransitionOnPositiveCross";
+ }
+
+ public class MotionController {
+ method public void addKey(androidx.constraintlayout.motion.widget.Key!);
+ method public int getAnimateRelativeTo();
+ method public void getCenter(double, float[]!, float[]!);
+ method public float getCenterX();
+ method public float getCenterY();
+ method public int getDrawPath();
+ method public float getFinalHeight();
+ method public float getFinalWidth();
+ method public float getFinalX();
+ method public float getFinalY();
+ method public int getKeyFrameInfo(int, int[]!);
+ method public int getKeyFramePositions(int[]!, float[]!);
+ method public float getStartHeight();
+ method public float getStartWidth();
+ method public float getStartX();
+ method public float getStartY();
+ method public int getTransformPivotTarget();
+ method public android.view.View! getView();
+ method public void remeasure();
+ method public void setDrawPath(int);
+ method public void setPathMotionArc(int);
+ method public void setStartState(androidx.constraintlayout.motion.utils.ViewState!, android.view.View!, int, int, int);
+ method public void setTransformPivotTarget(int);
+ method public void setView(android.view.View!);
+ method public void setup(int, int, float, long);
+ method public void setupRelative(androidx.constraintlayout.motion.widget.MotionController!);
+ field public static final int DRAW_PATH_AS_CONFIGURED = 4; // 0x4
+ field public static final int DRAW_PATH_BASIC = 1; // 0x1
+ field public static final int DRAW_PATH_CARTESIAN = 3; // 0x3
+ field public static final int DRAW_PATH_NONE = 0; // 0x0
+ field public static final int DRAW_PATH_RECTANGLE = 5; // 0x5
+ field public static final int DRAW_PATH_RELATIVE = 2; // 0x2
+ field public static final int DRAW_PATH_SCREEN = 6; // 0x6
+ field public static final int HORIZONTAL_PATH_X = 2; // 0x2
+ field public static final int HORIZONTAL_PATH_Y = 3; // 0x3
+ field public static final int PATH_PERCENT = 0; // 0x0
+ field public static final int PATH_PERPENDICULAR = 1; // 0x1
+ field public static final int ROTATION_LEFT = 2; // 0x2
+ field public static final int ROTATION_RIGHT = 1; // 0x1
+ field public static final int VERTICAL_PATH_X = 4; // 0x4
+ field public static final int VERTICAL_PATH_Y = 5; // 0x5
+ }
+
+ public class MotionHelper extends androidx.constraintlayout.widget.ConstraintHelper implements androidx.constraintlayout.motion.widget.MotionHelperInterface {
+ ctor public MotionHelper(android.content.Context!);
+ ctor public MotionHelper(android.content.Context!, android.util.AttributeSet!);
+ ctor public MotionHelper(android.content.Context!, android.util.AttributeSet!, int);
+ method public float getProgress();
+ method public boolean isDecorator();
+ method public boolean isUseOnHide();
+ method public boolean isUsedOnShow();
+ method public void onFinishedMotionScene(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public void onPostDraw(android.graphics.Canvas!);
+ method public void onPreDraw(android.graphics.Canvas!);
+ method public void onPreSetup(androidx.constraintlayout.motion.widget.MotionLayout!, java.util.HashMap<android.view.View!,androidx.constraintlayout.motion.widget.MotionController!>!);
+ method public void onTransitionChange(androidx.constraintlayout.motion.widget.MotionLayout!, int, int, float);
+ method public void onTransitionCompleted(androidx.constraintlayout.motion.widget.MotionLayout!, int);
+ method public void onTransitionStarted(androidx.constraintlayout.motion.widget.MotionLayout!, int, int);
+ method public void onTransitionTrigger(androidx.constraintlayout.motion.widget.MotionLayout!, int, boolean, float);
+ method public void setProgress(android.view.View!, float);
+ method public void setProgress(float);
+ field protected android.view.View![]! views;
+ }
+
+ public interface MotionHelperInterface extends androidx.constraintlayout.motion.widget.Animatable androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener {
+ method public boolean isDecorator();
+ method public boolean isUseOnHide();
+ method public boolean isUsedOnShow();
+ method public void onFinishedMotionScene(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public void onPostDraw(android.graphics.Canvas!);
+ method public void onPreDraw(android.graphics.Canvas!);
+ method public void onPreSetup(androidx.constraintlayout.motion.widget.MotionLayout!, java.util.HashMap<android.view.View!,androidx.constraintlayout.motion.widget.MotionController!>!);
+ }
+
+ public abstract class MotionInterpolator implements android.view.animation.Interpolator {
+ ctor public MotionInterpolator();
+ method public abstract float getVelocity();
+ }
+
+ public class MotionLayout extends androidx.constraintlayout.widget.ConstraintLayout implements androidx.core.view.NestedScrollingParent3 {
+ ctor public MotionLayout(android.content.Context);
+ ctor public MotionLayout(android.content.Context, android.util.AttributeSet?);
+ ctor public MotionLayout(android.content.Context, android.util.AttributeSet?, int);
+ method public void addTransitionListener(androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener!);
+ method public boolean applyViewTransition(int, androidx.constraintlayout.motion.widget.MotionController!);
+ method public androidx.constraintlayout.widget.ConstraintSet! cloneConstraintSet(int);
+ method public void enableTransition(int, boolean);
+ method public void enableViewTransition(int, boolean);
+ method protected void fireTransitionCompleted();
+ method public void fireTrigger(int, boolean, float);
+ method public androidx.constraintlayout.widget.ConstraintSet! getConstraintSet(int);
+ method @IdRes public int[]! getConstraintSetIds();
+ method public int getCurrentState();
+ method public java.util.ArrayList<androidx.constraintlayout.motion.widget.MotionScene.Transition!>! getDefinedTransitions();
+ method public androidx.constraintlayout.motion.widget.DesignTool! getDesignTool();
+ method public int getEndState();
+ method public int[]! getMatchingConstraintSetIds(java.lang.String!...!);
+ method protected long getNanoTime();
+ method public float getProgress();
+ method public androidx.constraintlayout.motion.widget.MotionScene! getScene();
+ method public int getStartState();
+ method public float getTargetPosition();
+ method public androidx.constraintlayout.motion.widget.MotionScene.Transition! getTransition(int);
+ method public android.os.Bundle! getTransitionState();
+ method public long getTransitionTimeMs();
+ method public float getVelocity();
+ method public void getViewVelocity(android.view.View!, float, float, float[]!, int);
+ method public boolean isDelayedApplicationOfInitialState();
+ method public boolean isInRotation();
+ method public boolean isInteractionEnabled();
+ method public boolean isViewTransitionEnabled(int);
+ method public void jumpToState(int);
+ method protected androidx.constraintlayout.motion.widget.MotionLayout.MotionTracker! obtainVelocityTracker();
+ method public void onNestedPreScroll(android.view.View, int, int, int[], int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int, int[]!);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
+ method public void onStopNestedScroll(android.view.View, int);
+ method @Deprecated public void rebuildMotion();
+ method public void rebuildScene();
+ method public boolean removeTransitionListener(androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener!);
+ method public void rotateTo(int, int);
+ method public void scheduleTransitionTo(int);
+ method public void setDebugMode(int);
+ method public void setDelayedApplicationOfInitialState(boolean);
+ method public void setInteractionEnabled(boolean);
+ method public void setInterpolatedProgress(float);
+ method public void setOnHide(float);
+ method public void setOnShow(float);
+ method public void setProgress(float);
+ method public void setProgress(float, float);
+ method public void setScene(androidx.constraintlayout.motion.widget.MotionScene!);
+ method protected void setTransition(androidx.constraintlayout.motion.widget.MotionScene.Transition!);
+ method public void setTransition(int);
+ method public void setTransition(int, int);
+ method public void setTransitionDuration(int);
+ method public void setTransitionListener(androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener!);
+ method public void setTransitionState(android.os.Bundle!);
+ method public void touchAnimateTo(int, float, float);
+ method public void touchSpringTo(float, float);
+ method public void transitionToEnd();
+ method public void transitionToEnd(Runnable!);
+ method public void transitionToStart();
+ method public void transitionToStart(Runnable!);
+ method public void transitionToState(int);
+ method public void transitionToState(int, int);
+ method public void transitionToState(int, int, int);
+ method public void transitionToState(int, int, int, int);
+ method public void updateState();
+ method public void updateState(int, androidx.constraintlayout.widget.ConstraintSet!);
+ method public void updateStateAnimate(int, androidx.constraintlayout.widget.ConstraintSet!, int);
+ method public void viewTransition(int, android.view.View!...!);
+ field public static final int DEBUG_SHOW_NONE = 0; // 0x0
+ field public static final int DEBUG_SHOW_PATH = 2; // 0x2
+ field public static final int DEBUG_SHOW_PROGRESS = 1; // 0x1
+ field public static boolean IS_IN_EDIT_MODE;
+ field public static final int TOUCH_UP_COMPLETE = 0; // 0x0
+ field public static final int TOUCH_UP_COMPLETE_TO_END = 2; // 0x2
+ field public static final int TOUCH_UP_COMPLETE_TO_START = 1; // 0x1
+ field public static final int TOUCH_UP_DECELERATE = 4; // 0x4
+ field public static final int TOUCH_UP_DECELERATE_AND_COMPLETE = 5; // 0x5
+ field public static final int TOUCH_UP_NEVER_TO_END = 7; // 0x7
+ field public static final int TOUCH_UP_NEVER_TO_START = 6; // 0x6
+ field public static final int TOUCH_UP_STOP = 3; // 0x3
+ field public static final int VELOCITY_LAYOUT = 1; // 0x1
+ field public static final int VELOCITY_POST_LAYOUT = 0; // 0x0
+ field public static final int VELOCITY_STATIC_LAYOUT = 3; // 0x3
+ field public static final int VELOCITY_STATIC_POST_LAYOUT = 2; // 0x2
+ field protected boolean mMeasureDuringTransition;
+ }
+
+ protected static interface MotionLayout.MotionTracker {
+ method public void addMovement(android.view.MotionEvent!);
+ method public void clear();
+ method public void computeCurrentVelocity(int);
+ method public void computeCurrentVelocity(int, float);
+ method public float getXVelocity();
+ method public float getXVelocity(int);
+ method public float getYVelocity();
+ method public float getYVelocity(int);
+ method public void recycle();
+ }
+
+ public static interface MotionLayout.TransitionListener {
+ method public void onTransitionChange(androidx.constraintlayout.motion.widget.MotionLayout!, int, int, float);
+ method public void onTransitionCompleted(androidx.constraintlayout.motion.widget.MotionLayout!, int);
+ method public void onTransitionStarted(androidx.constraintlayout.motion.widget.MotionLayout!, int, int);
+ method public void onTransitionTrigger(androidx.constraintlayout.motion.widget.MotionLayout!, int, boolean, float);
+ }
+
+ public class MotionScene {
+ ctor public MotionScene(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public void addOnClickListeners(androidx.constraintlayout.motion.widget.MotionLayout!, int);
+ method public void addTransition(androidx.constraintlayout.motion.widget.MotionScene.Transition!);
+ method public boolean applyViewTransition(int, androidx.constraintlayout.motion.widget.MotionController!);
+ method public androidx.constraintlayout.motion.widget.MotionScene.Transition! bestTransitionFor(int, float, float, android.view.MotionEvent!);
+ method public void disableAutoTransition(boolean);
+ method public void enableViewTransition(int, boolean);
+ method public int gatPathMotionArc();
+ method public androidx.constraintlayout.widget.ConstraintSet! getConstraintSet(android.content.Context!, String!);
+ method public int[]! getConstraintSetIds();
+ method public java.util.ArrayList<androidx.constraintlayout.motion.widget.MotionScene.Transition!>! getDefinedTransitions();
+ method public int getDuration();
+ method public android.view.animation.Interpolator! getInterpolator();
+ method public void getKeyFrames(androidx.constraintlayout.motion.widget.MotionController!);
+ method public int[]! getMatchingStateLabels(java.lang.String!...!);
+ method public float getPathPercent(android.view.View!, int);
+ method public float getStaggered();
+ method public androidx.constraintlayout.motion.widget.MotionScene.Transition! getTransitionById(int);
+ method public java.util.List<androidx.constraintlayout.motion.widget.MotionScene.Transition!>! getTransitionsWithState(int);
+ method public boolean isViewTransitionEnabled(int);
+ method public int lookUpConstraintId(String!);
+ method public String! lookUpConstraintName(int);
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void removeTransition(androidx.constraintlayout.motion.widget.MotionScene.Transition!);
+ method public void setConstraintSet(int, androidx.constraintlayout.widget.ConstraintSet!);
+ method public void setDuration(int);
+ method public void setKeyframe(android.view.View!, int, String!, Object!);
+ method public void setRtl(boolean);
+ method public void setTransition(androidx.constraintlayout.motion.widget.MotionScene.Transition!);
+ method public static String! stripID(String!);
+ method public boolean validateLayout(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public void viewTransition(int, android.view.View!...!);
+ field public static final int LAYOUT_CALL_MEASURE = 2; // 0x2
+ field public static final int LAYOUT_HONOR_REQUEST = 1; // 0x1
+ field public static final int LAYOUT_IGNORE_REQUEST = 0; // 0x0
+ field public static final int UNSET = -1; // 0xffffffff
+ }
+
+ public static class MotionScene.Transition {
+ ctor public MotionScene.Transition(int, androidx.constraintlayout.motion.widget.MotionScene!, int, int);
+ method public void addKeyFrame(androidx.constraintlayout.motion.widget.KeyFrames!);
+ method public void addOnClick(android.content.Context!, org.xmlpull.v1.XmlPullParser!);
+ method public void addOnClick(int, int);
+ method public String! debugString(android.content.Context!);
+ method public int getAutoTransition();
+ method public int getDuration();
+ method public int getEndConstraintSetId();
+ method public int getId();
+ method public java.util.List<androidx.constraintlayout.motion.widget.KeyFrames!>! getKeyFrameList();
+ method public int getLayoutDuringTransition();
+ method public java.util.List<androidx.constraintlayout.motion.widget.MotionScene.Transition.TransitionOnClick!>! getOnClickList();
+ method public int getPathMotionArc();
+ method public float getStagger();
+ method public int getStartConstraintSetId();
+ method public androidx.constraintlayout.motion.widget.TouchResponse! getTouchResponse();
+ method public boolean isEnabled();
+ method public boolean isTransitionFlag(int);
+ method public void removeOnClick(int);
+ method public void setAutoTransition(int);
+ method public void setDuration(int);
+ method public void setEnabled(boolean);
+ method public void setInterpolatorInfo(int, String!, int);
+ method public void setLayoutDuringTransition(int);
+ method public void setOnSwipe(androidx.constraintlayout.motion.widget.OnSwipe!);
+ method public void setOnTouchUp(int);
+ method public void setPathMotionArc(int);
+ method public void setStagger(float);
+ method public void setTransitionFlag(int);
+ field public static final int AUTO_ANIMATE_TO_END = 4; // 0x4
+ field public static final int AUTO_ANIMATE_TO_START = 3; // 0x3
+ field public static final int AUTO_JUMP_TO_END = 2; // 0x2
+ field public static final int AUTO_JUMP_TO_START = 1; // 0x1
+ field public static final int AUTO_NONE = 0; // 0x0
+ field public static final int INTERPOLATE_ANTICIPATE = 6; // 0x6
+ field public static final int INTERPOLATE_BOUNCE = 4; // 0x4
+ field public static final int INTERPOLATE_EASE_IN = 1; // 0x1
+ field public static final int INTERPOLATE_EASE_IN_OUT = 0; // 0x0
+ field public static final int INTERPOLATE_EASE_OUT = 2; // 0x2
+ field public static final int INTERPOLATE_LINEAR = 3; // 0x3
+ field public static final int INTERPOLATE_OVERSHOOT = 5; // 0x5
+ field public static final int INTERPOLATE_REFERENCE_ID = -2; // 0xfffffffe
+ field public static final int INTERPOLATE_SPLINE_STRING = -1; // 0xffffffff
+ }
+
+ public static class MotionScene.Transition.TransitionOnClick implements android.view.View.OnClickListener {
+ ctor public MotionScene.Transition.TransitionOnClick(android.content.Context!, androidx.constraintlayout.motion.widget.MotionScene.Transition!, org.xmlpull.v1.XmlPullParser!);
+ ctor public MotionScene.Transition.TransitionOnClick(androidx.constraintlayout.motion.widget.MotionScene.Transition!, int, int);
+ method public void addOnClickListeners(androidx.constraintlayout.motion.widget.MotionLayout!, int, androidx.constraintlayout.motion.widget.MotionScene.Transition!);
+ method public void onClick(android.view.View!);
+ method public void removeOnClickListeners(androidx.constraintlayout.motion.widget.MotionLayout!);
+ field public static final int ANIM_TOGGLE = 17; // 0x11
+ field public static final int ANIM_TO_END = 1; // 0x1
+ field public static final int ANIM_TO_START = 16; // 0x10
+ field public static final int JUMP_TO_END = 256; // 0x100
+ field public static final int JUMP_TO_START = 4096; // 0x1000
+ }
+
+ public class OnSwipe {
+ ctor public OnSwipe();
+ method public int getAutoCompleteMode();
+ method public int getDragDirection();
+ method public float getDragScale();
+ method public float getDragThreshold();
+ method public int getLimitBoundsTo();
+ method public float getMaxAcceleration();
+ method public float getMaxVelocity();
+ method public boolean getMoveWhenScrollAtTop();
+ method public int getNestedScrollFlags();
+ method public int getOnTouchUp();
+ method public int getRotationCenterId();
+ method public int getSpringBoundary();
+ method public float getSpringDamping();
+ method public float getSpringMass();
+ method public float getSpringStiffness();
+ method public float getSpringStopThreshold();
+ method public int getTouchAnchorId();
+ method public int getTouchAnchorSide();
+ method public int getTouchRegionId();
+ method public void setAutoCompleteMode(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setDragDirection(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setDragScale(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setDragThreshold(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setLimitBoundsTo(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setMaxAcceleration(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setMaxVelocity(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setMoveWhenScrollAtTop(boolean);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setNestedScrollFlags(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setOnTouchUp(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setRotateCenter(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setSpringBoundary(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setSpringDamping(float);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setSpringMass(float);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setSpringStiffness(float);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setSpringStopThreshold(float);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setTouchAnchorId(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setTouchAnchorSide(int);
+ method public androidx.constraintlayout.motion.widget.OnSwipe! setTouchRegionId(int);
+ field public static final int COMPLETE_MODE_CONTINUOUS_VELOCITY = 0; // 0x0
+ field public static final int COMPLETE_MODE_SPRING = 1; // 0x1
+ field public static final int DRAG_ANTICLOCKWISE = 7; // 0x7
+ field public static final int DRAG_CLOCKWISE = 6; // 0x6
+ field public static final int DRAG_DOWN = 1; // 0x1
+ field public static final int DRAG_END = 5; // 0x5
+ field public static final int DRAG_LEFT = 2; // 0x2
+ field public static final int DRAG_RIGHT = 3; // 0x3
+ field public static final int DRAG_START = 4; // 0x4
+ field public static final int DRAG_UP = 0; // 0x0
+ field public static final int FLAG_DISABLE_POST_SCROLL = 1; // 0x1
+ field public static final int FLAG_DISABLE_SCROLL = 2; // 0x2
+ field public static final int ON_UP_AUTOCOMPLETE = 0; // 0x0
+ field public static final int ON_UP_AUTOCOMPLETE_TO_END = 2; // 0x2
+ field public static final int ON_UP_AUTOCOMPLETE_TO_START = 1; // 0x1
+ field public static final int ON_UP_DECELERATE = 4; // 0x4
+ field public static final int ON_UP_DECELERATE_AND_COMPLETE = 5; // 0x5
+ field public static final int ON_UP_NEVER_TO_END = 7; // 0x7
+ field public static final int ON_UP_NEVER_TO_START = 6; // 0x6
+ field public static final int ON_UP_STOP = 3; // 0x3
+ field public static final int SIDE_BOTTOM = 3; // 0x3
+ field public static final int SIDE_END = 6; // 0x6
+ field public static final int SIDE_LEFT = 1; // 0x1
+ field public static final int SIDE_MIDDLE = 4; // 0x4
+ field public static final int SIDE_RIGHT = 2; // 0x2
+ field public static final int SIDE_START = 5; // 0x5
+ field public static final int SIDE_TOP = 0; // 0x0
+ field public static final int SPRING_BOUNDARY_BOUNCEBOTH = 3; // 0x3
+ field public static final int SPRING_BOUNDARY_BOUNCEEND = 2; // 0x2
+ field public static final int SPRING_BOUNDARY_BOUNCESTART = 1; // 0x1
+ field public static final int SPRING_BOUNDARY_OVERSHOOT = 0; // 0x0
+ }
+
+ public abstract class TransitionAdapter implements androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener {
+ ctor public TransitionAdapter();
+ method public void onTransitionChange(androidx.constraintlayout.motion.widget.MotionLayout!, int, int, float);
+ method public void onTransitionCompleted(androidx.constraintlayout.motion.widget.MotionLayout!, int);
+ method public void onTransitionStarted(androidx.constraintlayout.motion.widget.MotionLayout!, int, int);
+ method public void onTransitionTrigger(androidx.constraintlayout.motion.widget.MotionLayout!, int, boolean, float);
+ }
+
+ public class TransitionBuilder {
+ ctor public TransitionBuilder();
+ method public static androidx.constraintlayout.motion.widget.MotionScene.Transition! buildTransition(androidx.constraintlayout.motion.widget.MotionScene!, int, int, androidx.constraintlayout.widget.ConstraintSet!, int, androidx.constraintlayout.widget.ConstraintSet!);
+ method public static void validate(androidx.constraintlayout.motion.widget.MotionLayout!);
+ }
+
+ public class ViewTransition {
+ method public int getSharedValue();
+ method public int getSharedValueCurrent();
+ method public int getSharedValueID();
+ method public int getStateTransition();
+ method public void setSharedValue(int);
+ method public void setSharedValueCurrent(int);
+ method public void setSharedValueID(int);
+ method public void setStateTransition(int);
+ field public static final String CONSTRAINT_OVERRIDE = "ConstraintOverride";
+ field public static final String CUSTOM_ATTRIBUTE = "CustomAttribute";
+ field public static final String CUSTOM_METHOD = "CustomMethod";
+ field public static final String KEY_FRAME_SET_TAG = "KeyFrameSet";
+ field public static final int ONSTATE_ACTION_DOWN = 1; // 0x1
+ field public static final int ONSTATE_ACTION_DOWN_UP = 3; // 0x3
+ field public static final int ONSTATE_ACTION_UP = 2; // 0x2
+ field public static final int ONSTATE_SHARED_VALUE_SET = 4; // 0x4
+ field public static final int ONSTATE_SHARED_VALUE_UNSET = 5; // 0x5
+ field public static final String VIEW_TRANSITION_TAG = "ViewTransition";
+ }
+
+ public class ViewTransitionController {
+ ctor public ViewTransitionController(androidx.constraintlayout.motion.widget.MotionLayout!);
+ method public void add(androidx.constraintlayout.motion.widget.ViewTransition!);
+ }
+
+}
+
+package androidx.constraintlayout.utils.widget {
+
+ public class ImageFilterButton extends androidx.appcompat.widget.AppCompatImageButton {
+ ctor public ImageFilterButton(android.content.Context!);
+ ctor public ImageFilterButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public ImageFilterButton(android.content.Context!, android.util.AttributeSet!, int);
+ method public float getContrast();
+ method public float getCrossfade();
+ method public float getImagePanX();
+ method public float getImagePanY();
+ method public float getImageRotate();
+ method public float getImageZoom();
+ method public float getRound();
+ method public float getRoundPercent();
+ method public float getSaturation();
+ method public float getWarmth();
+ method public void setAltImageResource(int);
+ method public void setBrightness(float);
+ method public void setContrast(float);
+ method public void setCrossfade(float);
+ method public void setImagePanX(float);
+ method public void setImagePanY(float);
+ method public void setImageRotate(float);
+ method public void setImageZoom(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRound(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRoundPercent(float);
+ method public void setSaturation(float);
+ method public void setWarmth(float);
+ }
+
+ public class ImageFilterView extends androidx.appcompat.widget.AppCompatImageView {
+ ctor public ImageFilterView(android.content.Context!);
+ ctor public ImageFilterView(android.content.Context!, android.util.AttributeSet!);
+ ctor public ImageFilterView(android.content.Context!, android.util.AttributeSet!, int);
+ method public float getBrightness();
+ method public float getContrast();
+ method public float getCrossfade();
+ method public float getImagePanX();
+ method public float getImagePanY();
+ method public float getImageRotate();
+ method public float getImageZoom();
+ method public float getRound();
+ method public float getRoundPercent();
+ method public float getSaturation();
+ method public float getWarmth();
+ method public void setAltImageDrawable(android.graphics.drawable.Drawable!);
+ method public void setAltImageResource(int);
+ method public void setBrightness(float);
+ method public void setContrast(float);
+ method public void setCrossfade(float);
+ method public void setImagePanX(float);
+ method public void setImagePanY(float);
+ method public void setImageRotate(float);
+ method public void setImageZoom(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRound(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRoundPercent(float);
+ method public void setSaturation(float);
+ method public void setWarmth(float);
+ }
+
+ public class MockView extends android.view.View {
+ ctor public MockView(android.content.Context!);
+ ctor public MockView(android.content.Context!, android.util.AttributeSet!);
+ ctor public MockView(android.content.Context!, android.util.AttributeSet!, int);
+ method public void onDraw(android.graphics.Canvas);
+ field protected String! mText;
+ }
+
+ public class MotionButton extends androidx.appcompat.widget.AppCompatButton {
+ ctor public MotionButton(android.content.Context!);
+ ctor public MotionButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public MotionButton(android.content.Context!, android.util.AttributeSet!, int);
+ method public float getRound();
+ method public float getRoundPercent();
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRound(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRoundPercent(float);
+ }
+
+ public class MotionLabel extends android.view.View implements androidx.constraintlayout.motion.widget.FloatLayout {
+ ctor public MotionLabel(android.content.Context!);
+ ctor public MotionLabel(android.content.Context!, android.util.AttributeSet?);
+ ctor public MotionLabel(android.content.Context!, android.util.AttributeSet?, int);
+ method public float getRound();
+ method public float getRoundPercent();
+ method public float getScaleFromTextSize();
+ method public float getTextBackgroundPanX();
+ method public float getTextBackgroundPanY();
+ method public float getTextBackgroundRotate();
+ method public float getTextBackgroundZoom();
+ method public int getTextOutlineColor();
+ method public float getTextPanX();
+ method public float getTextPanY();
+ method public float getTextureHeight();
+ method public float getTextureWidth();
+ method public android.graphics.Typeface! getTypeface();
+ method public void layout(float, float, float, float);
+ method public void setGravity(int);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRound(float);
+ method @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public void setRoundPercent(float);
+ method public void setScaleFromTextSize(float);
+ method public void setText(CharSequence!);
+ method public void setTextBackgroundPanX(float);
+ method public void setTextBackgroundPanY(float);
+ method public void setTextBackgroundRotate(float);
+ method public void setTextBackgroundZoom(float);
+ method public void setTextFillColor(int);
+ method public void setTextOutlineColor(int);
+ method public void setTextOutlineThickness(float);
+ method public void setTextPanX(float);
+ method public void setTextPanY(float);
+ method public void setTextSize(float);
+ method public void setTextureHeight(float);
+ method public void setTextureWidth(float);
+ method public void setTypeface(android.graphics.Typeface!);
+ }
+
+ public class MotionTelltales extends androidx.constraintlayout.utils.widget.MockView {
+ ctor public MotionTelltales(android.content.Context!);
+ ctor public MotionTelltales(android.content.Context!, android.util.AttributeSet!);
+ ctor public MotionTelltales(android.content.Context!, android.util.AttributeSet!, int);
+ method public void setText(CharSequence!);
+ }
+
+}
+
+package androidx.constraintlayout.widget {
+
+ public class Barrier extends androidx.constraintlayout.widget.ConstraintHelper {
+ ctor public Barrier(android.content.Context!);
+ ctor public Barrier(android.content.Context!, android.util.AttributeSet!);
+ ctor public Barrier(android.content.Context!, android.util.AttributeSet!, int);
+ method @Deprecated public boolean allowsGoneWidget();
+ method public boolean getAllowsGoneWidget();
+ method public int getMargin();
+ method public int getType();
+ method public void setAllowsGoneWidget(boolean);
+ method public void setDpMargin(int);
+ method public void setMargin(int);
+ method public void setType(int);
+ field public static final int BOTTOM = 3; // 0x3
+ field public static final int END = 6; // 0x6
+ field public static final int LEFT = 0; // 0x0
+ field public static final int RIGHT = 1; // 0x1
+ field public static final int START = 5; // 0x5
+ field public static final int TOP = 2; // 0x2
+ }
+
+ public class ConstraintAttribute {
+ ctor public ConstraintAttribute(androidx.constraintlayout.widget.ConstraintAttribute!, Object!);
+ ctor public ConstraintAttribute(String!, androidx.constraintlayout.widget.ConstraintAttribute.AttributeType!);
+ ctor public ConstraintAttribute(String!, androidx.constraintlayout.widget.ConstraintAttribute.AttributeType!, Object!, boolean);
+ method public void applyCustom(android.view.View!);
+ method public boolean diff(androidx.constraintlayout.widget.ConstraintAttribute!);
+ method public static java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>! extractAttributes(java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>!, android.view.View!);
+ method public int getColorValue();
+ method public float getFloatValue();
+ method public int getIntegerValue();
+ method public String! getName();
+ method public String! getStringValue();
+ method public androidx.constraintlayout.widget.ConstraintAttribute.AttributeType! getType();
+ method public float getValueToInterpolate();
+ method public void getValuesToInterpolate(float[]!);
+ method public boolean isBooleanValue();
+ method public boolean isContinuous();
+ method public boolean isMethod();
+ method public int numberOfInterpolatedValues();
+ method public static void parse(android.content.Context!, org.xmlpull.v1.XmlPullParser!, java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public static void setAttributes(android.view.View!, java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>!);
+ method public void setColorValue(int);
+ method public void setFloatValue(float);
+ method public void setIntValue(int);
+ method public void setStringValue(String!);
+ method public void setValue(float[]!);
+ method public void setValue(Object!);
+ }
+
+ public enum ConstraintAttribute.AttributeType {
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType BOOLEAN_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType COLOR_DRAWABLE_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType COLOR_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType DIMENSION_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType FLOAT_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType INT_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType REFERENCE_TYPE;
+ enum_constant public static final androidx.constraintlayout.widget.ConstraintAttribute.AttributeType STRING_TYPE;
+ }
+
+ public abstract class ConstraintHelper extends android.view.View {
+ ctor public ConstraintHelper(android.content.Context!);
+ ctor public ConstraintHelper(android.content.Context!, android.util.AttributeSet!);
+ ctor public ConstraintHelper(android.content.Context!, android.util.AttributeSet!, int);
+ method public void addView(android.view.View!);
+ method public void applyHelperParams();
+ method protected void applyLayoutFeatures();
+ method protected void applyLayoutFeatures(androidx.constraintlayout.widget.ConstraintLayout!);
+ method protected void applyLayoutFeaturesInConstraintSet(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public boolean containsId(int);
+ method public int[]! getReferencedIds();
+ method protected android.view.View![]! getViews(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public int indexFromId(int);
+ method protected void init(android.util.AttributeSet!);
+ method public static boolean isChildOfHelper(android.view.View!);
+ method public void loadParameters(androidx.constraintlayout.widget.ConstraintSet.Constraint!, androidx.constraintlayout.core.widgets.HelperWidget!, androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!, android.util.SparseArray<androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public void onDraw(android.graphics.Canvas);
+ method public int removeView(android.view.View!);
+ method public void resolveRtl(androidx.constraintlayout.core.widgets.ConstraintWidget!, boolean);
+ method protected void setIds(String!);
+ method protected void setReferenceTags(String!);
+ method public void setReferencedIds(int[]!);
+ method public void updatePostConstraints(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void updatePostLayout(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void updatePostMeasure(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void updatePreDraw(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void updatePreLayout(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, androidx.constraintlayout.core.widgets.Helper!, android.util.SparseArray<androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public void updatePreLayout(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void validateParams();
+ field protected static final String CHILD_TAG = "CONSTRAINT_LAYOUT_HELPER_CHILD";
+ field protected int mCount;
+ field protected androidx.constraintlayout.core.widgets.Helper! mHelperWidget;
+ field protected int[]! mIds;
+ field protected java.util.HashMap<java.lang.Integer!,java.lang.String!>! mMap;
+ field protected String! mReferenceIds;
+ field protected String! mReferenceTags;
+ field protected boolean mUseViewMeasure;
+ field protected android.content.Context! myContext;
+ }
+
+ public class ConstraintLayout extends android.view.ViewGroup {
+ ctor public ConstraintLayout(android.content.Context);
+ ctor public ConstraintLayout(android.content.Context, android.util.AttributeSet?);
+ ctor public ConstraintLayout(android.content.Context, android.util.AttributeSet?, int);
+ ctor public ConstraintLayout(android.content.Context, android.util.AttributeSet?, int, int);
+ method public void addValueModifier(androidx.constraintlayout.widget.ConstraintLayout.ValueModifier!);
+ method protected void applyConstraintsFromLayoutParams(boolean, android.view.View!, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!, android.util.SparseArray<androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method protected boolean dynamicUpdateConstraints(int, int);
+ method public void fillMetrics(androidx.constraintlayout.core.Metrics!);
+ method protected androidx.constraintlayout.widget.ConstraintLayout.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.constraintlayout.widget.ConstraintLayout.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
+ method public Object! getDesignInformation(int, Object!);
+ method public int getMaxHeight();
+ method public int getMaxWidth();
+ method public int getMinHeight();
+ method public int getMinWidth();
+ method public int getOptimizationLevel();
+ method public String! getSceneString();
+ method public static androidx.constraintlayout.widget.SharedValues! getSharedValues();
+ method public android.view.View! getViewById(int);
+ method public final androidx.constraintlayout.core.widgets.ConstraintWidget! getViewWidget(android.view.View!);
+ method protected boolean isRtl();
+ method public void loadLayoutDescription(int);
+ method protected void parseLayoutDescription(int);
+ method protected void resolveMeasuredDimension(int, int, int, int, boolean, boolean);
+ method protected void resolveSystem(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, int, int, int);
+ method public void setConstraintSet(androidx.constraintlayout.widget.ConstraintSet!);
+ method public void setDesignInformation(int, Object!, Object!);
+ method public void setMaxHeight(int);
+ method public void setMaxWidth(int);
+ method public void setMinHeight(int);
+ method public void setMinWidth(int);
+ method public void setOnConstraintsChanged(androidx.constraintlayout.widget.ConstraintsChangedListener!);
+ method public void setOptimizationLevel(int);
+ method protected void setSelfDimensionBehaviour(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, int, int, int, int);
+ method public void setState(int, int, int);
+ field public static final int DESIGN_INFO_ID = 0; // 0x0
+ field public static final String VERSION = "ConstraintLayout-2.2.0-alpha04";
+ field protected androidx.constraintlayout.widget.ConstraintLayoutStates! mConstraintLayoutSpec;
+ field protected boolean mDirtyHierarchy;
+ field protected androidx.constraintlayout.core.widgets.ConstraintWidgetContainer! mLayoutWidget;
+ }
+
+ public static class ConstraintLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public ConstraintLayout.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public ConstraintLayout.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public ConstraintLayout.LayoutParams(int, int);
+ method public String! getConstraintTag();
+ method public androidx.constraintlayout.core.widgets.ConstraintWidget! getConstraintWidget();
+ method public void reset();
+ method public void setWidgetDebugName(String!);
+ method public void validate();
+ field public static final int BASELINE = 5; // 0x5
+ field public static final int BOTTOM = 4; // 0x4
+ field public static final int CHAIN_PACKED = 2; // 0x2
+ field public static final int CHAIN_SPREAD = 0; // 0x0
+ field public static final int CHAIN_SPREAD_INSIDE = 1; // 0x1
+ field public static final int CIRCLE = 8; // 0x8
+ field public static final int END = 7; // 0x7
+ field public static final int GONE_UNSET = -2147483648; // 0x80000000
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int LEFT = 1; // 0x1
+ field public static final int MATCH_CONSTRAINT = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_PERCENT = 2; // 0x2
+ field public static final int MATCH_CONSTRAINT_SPREAD = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_WRAP = 1; // 0x1
+ field public static final int PARENT_ID = 0; // 0x0
+ field public static final int RIGHT = 2; // 0x2
+ field public static final int START = 6; // 0x6
+ field public static final int TOP = 3; // 0x3
+ field public static final int UNSET = -1; // 0xffffffff
+ field public static final int VERTICAL = 1; // 0x1
+ field public static final int WRAP_BEHAVIOR_HORIZONTAL_ONLY = 1; // 0x1
+ field public static final int WRAP_BEHAVIOR_INCLUDED = 0; // 0x0
+ field public static final int WRAP_BEHAVIOR_SKIPPED = 3; // 0x3
+ field public static final int WRAP_BEHAVIOR_VERTICAL_ONLY = 2; // 0x2
+ field public int baselineMargin;
+ field public int baselineToBaseline;
+ field public int baselineToBottom;
+ field public int baselineToTop;
+ field public int bottomToBottom;
+ field public int bottomToTop;
+ field public float circleAngle;
+ field public int circleConstraint;
+ field public int circleRadius;
+ field public boolean constrainedHeight;
+ field public boolean constrainedWidth;
+ field public String! constraintTag;
+ field public String! dimensionRatio;
+ field public int editorAbsoluteX;
+ field public int editorAbsoluteY;
+ field public int endToEnd;
+ field public int endToStart;
+ field public int goneBaselineMargin;
+ field public int goneBottomMargin;
+ field public int goneEndMargin;
+ field public int goneLeftMargin;
+ field public int goneRightMargin;
+ field public int goneStartMargin;
+ field public int goneTopMargin;
+ field public int guideBegin;
+ field public int guideEnd;
+ field public float guidePercent;
+ field public boolean guidelineUseRtl;
+ field public boolean helped;
+ field public float horizontalBias;
+ field public int horizontalChainStyle;
+ field public float horizontalWeight;
+ field public int leftToLeft;
+ field public int leftToRight;
+ field public int matchConstraintDefaultHeight;
+ field public int matchConstraintDefaultWidth;
+ field public int matchConstraintMaxHeight;
+ field public int matchConstraintMaxWidth;
+ field public int matchConstraintMinHeight;
+ field public int matchConstraintMinWidth;
+ field public float matchConstraintPercentHeight;
+ field public float matchConstraintPercentWidth;
+ field public int orientation;
+ field public int rightToLeft;
+ field public int rightToRight;
+ field public int startToEnd;
+ field public int startToStart;
+ field public int topToBottom;
+ field public int topToTop;
+ field public float verticalBias;
+ field public int verticalChainStyle;
+ field public float verticalWeight;
+ field public int wrapBehaviorInParent;
+ }
+
+ public static interface ConstraintLayout.ValueModifier {
+ method public boolean update(int, int, int, android.view.View!, androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!);
+ }
+
+ public class ConstraintLayoutStates {
+ method public boolean needsToChange(int, float, float);
+ method public void setOnConstraintsChanged(androidx.constraintlayout.widget.ConstraintsChangedListener!);
+ method public void updateConstraints(int, float, float);
+ field public static final String TAG = "ConstraintLayoutStates";
+ }
+
+ public class ConstraintLayoutStatistics {
+ ctor public ConstraintLayoutStatistics(androidx.constraintlayout.widget.ConstraintLayout!);
+ ctor public ConstraintLayoutStatistics(androidx.constraintlayout.widget.ConstraintLayoutStatistics!);
+ method public void attach(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public androidx.constraintlayout.widget.ConstraintLayoutStatistics! clone();
+ method public void detach();
+ method public long getValue(int);
+ method public void logSummary(String!);
+ method public void logSummary(String!, androidx.constraintlayout.widget.ConstraintLayoutStatistics!);
+ method public void reset();
+ field public static final int DURATION_OF_CHILD_MEASURES = 5; // 0x5
+ field public static final int DURATION_OF_LAYOUT = 7; // 0x7
+ field public static final int DURATION_OF_MEASURES = 6; // 0x6
+ field public static final int NUMBER_OF_CHILD_MEASURES = 4; // 0x4
+ field public static final int NUMBER_OF_CHILD_VIEWS = 3; // 0x3
+ field public static final int NUMBER_OF_EQUATIONS = 9; // 0x9
+ field public static final int NUMBER_OF_LAYOUTS = 1; // 0x1
+ field public static final int NUMBER_OF_ON_MEASURES = 2; // 0x2
+ field public static final int NUMBER_OF_SIMPLE_EQUATIONS = 10; // 0xa
+ field public static final int NUMBER_OF_VARIABLES = 8; // 0x8
+ }
+
+ public class ConstraintProperties {
+ ctor public ConstraintProperties(android.view.View!);
+ method public androidx.constraintlayout.widget.ConstraintProperties! addToHorizontalChain(int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! addToHorizontalChainRTL(int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! addToVerticalChain(int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! alpha(float);
+ method public void apply();
+ method public androidx.constraintlayout.widget.ConstraintProperties! center(int, int, int, int, int, int, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerHorizontally(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerHorizontally(int, int, int, int, int, int, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerHorizontallyRtl(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerHorizontallyRtl(int, int, int, int, int, int, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerVertically(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! centerVertically(int, int, int, int, int, int, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! connect(int, int, int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainDefaultHeight(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainDefaultWidth(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainHeight(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainMaxHeight(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainMaxWidth(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainMinHeight(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainMinWidth(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! constrainWidth(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! dimensionRatio(String!);
+ method public androidx.constraintlayout.widget.ConstraintProperties! elevation(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! goneMargin(int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! horizontalBias(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! horizontalChainStyle(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! horizontalWeight(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! margin(int, int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! removeConstraints(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! removeFromHorizontalChain();
+ method public androidx.constraintlayout.widget.ConstraintProperties! removeFromVerticalChain();
+ method public androidx.constraintlayout.widget.ConstraintProperties! rotation(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! rotationX(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! rotationY(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! scaleX(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! scaleY(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! transformPivot(float, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! transformPivotX(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! transformPivotY(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! translation(float, float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! translationX(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! translationY(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! translationZ(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! verticalBias(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! verticalChainStyle(int);
+ method public androidx.constraintlayout.widget.ConstraintProperties! verticalWeight(float);
+ method public androidx.constraintlayout.widget.ConstraintProperties! visibility(int);
+ field public static final int BASELINE = 5; // 0x5
+ field public static final int BOTTOM = 4; // 0x4
+ field public static final int END = 7; // 0x7
+ field public static final int LEFT = 1; // 0x1
+ field public static final int MATCH_CONSTRAINT = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_SPREAD = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_WRAP = 1; // 0x1
+ field public static final int PARENT_ID = 0; // 0x0
+ field public static final int RIGHT = 2; // 0x2
+ field public static final int START = 6; // 0x6
+ field public static final int TOP = 3; // 0x3
+ field public static final int UNSET = -1; // 0xffffffff
+ field public static final int WRAP_CONTENT = -2; // 0xfffffffe
+ }
+
+ public class ConstraintSet {
+ ctor public ConstraintSet();
+ method public void addColorAttributes(java.lang.String!...!);
+ method public void addFloatAttributes(java.lang.String!...!);
+ method public void addIntAttributes(java.lang.String!...!);
+ method public void addStringAttributes(java.lang.String!...!);
+ method public void addToHorizontalChain(int, int, int);
+ method public void addToHorizontalChainRTL(int, int, int);
+ method public void addToVerticalChain(int, int, int);
+ method public void applyCustomAttributes(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void applyDeltaFrom(androidx.constraintlayout.widget.ConstraintSet!);
+ method public void applyTo(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void applyToHelper(androidx.constraintlayout.widget.ConstraintHelper!, androidx.constraintlayout.core.widgets.ConstraintWidget!, androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!, android.util.SparseArray<androidx.constraintlayout.core.widgets.ConstraintWidget!>!);
+ method public void applyToLayoutParams(int, androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!);
+ method public void applyToWithoutCustom(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public static androidx.constraintlayout.widget.ConstraintSet.Constraint! buildDelta(android.content.Context!, org.xmlpull.v1.XmlPullParser!);
+ method public void center(int, int, int, int, int, int, int, float);
+ method public void centerHorizontally(int, int);
+ method public void centerHorizontally(int, int, int, int, int, int, int, float);
+ method public void centerHorizontallyRtl(int, int);
+ method public void centerHorizontallyRtl(int, int, int, int, int, int, int, float);
+ method public void centerVertically(int, int);
+ method public void centerVertically(int, int, int, int, int, int, int, float);
+ method public void clear(int);
+ method public void clear(int, int);
+ method public void clone(android.content.Context!, int);
+ method public void clone(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void clone(androidx.constraintlayout.widget.Constraints!);
+ method public void clone(androidx.constraintlayout.widget.ConstraintSet!);
+ method public void connect(int, int, int, int);
+ method public void connect(int, int, int, int, int);
+ method public void constrainCircle(int, int, int, float);
+ method public void constrainDefaultHeight(int, int);
+ method public void constrainDefaultWidth(int, int);
+ method public void constrainHeight(int, int);
+ method public void constrainMaxHeight(int, int);
+ method public void constrainMaxWidth(int, int);
+ method public void constrainMinHeight(int, int);
+ method public void constrainMinWidth(int, int);
+ method public void constrainPercentHeight(int, float);
+ method public void constrainPercentWidth(int, float);
+ method public void constrainWidth(int, int);
+ method public void constrainedHeight(int, boolean);
+ method public void constrainedWidth(int, boolean);
+ method public void create(int, int);
+ method public void createBarrier(int, int, int, int...!);
+ method public void createHorizontalChain(int, int, int, int, int[]!, float[]!, int);
+ method public void createHorizontalChainRtl(int, int, int, int, int[]!, float[]!, int);
+ method public void createVerticalChain(int, int, int, int, int[]!, float[]!, int);
+ method public void dump(androidx.constraintlayout.motion.widget.MotionScene!, int...!);
+ method public boolean getApplyElevation(int);
+ method public androidx.constraintlayout.widget.ConstraintSet.Constraint! getConstraint(int);
+ method public java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>! getCustomAttributeSet();
+ method public int getHeight(int);
+ method public int[]! getKnownIds();
+ method public androidx.constraintlayout.widget.ConstraintSet.Constraint! getParameters(int);
+ method public int[]! getReferencedIds(int);
+ method public String![]! getStateLabels();
+ method public int getVisibility(int);
+ method public int getVisibilityMode(int);
+ method public int getWidth(int);
+ method public boolean isForceId();
+ method public boolean isValidateOnParse();
+ method public void load(android.content.Context!, int);
+ method public void load(android.content.Context!, org.xmlpull.v1.XmlPullParser!);
+ method public boolean matchesLabels(java.lang.String!...!);
+ method public void parseColorAttributes(androidx.constraintlayout.widget.ConstraintSet.Constraint!, String!);
+ method public void parseFloatAttributes(androidx.constraintlayout.widget.ConstraintSet.Constraint!, String!);
+ method public void parseIntAttributes(androidx.constraintlayout.widget.ConstraintSet.Constraint!, String!);
+ method public void parseStringAttributes(androidx.constraintlayout.widget.ConstraintSet.Constraint!, String!);
+ method public void readFallback(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void readFallback(androidx.constraintlayout.widget.ConstraintSet!);
+ method public void removeAttribute(String!);
+ method public void removeFromHorizontalChain(int);
+ method public void removeFromVerticalChain(int);
+ method public void setAlpha(int, float);
+ method public void setApplyElevation(int, boolean);
+ method public void setBarrierType(int, int);
+ method public void setColorValue(int, String!, int);
+ method public void setDimensionRatio(int, String!);
+ method public void setEditorAbsoluteX(int, int);
+ method public void setEditorAbsoluteY(int, int);
+ method public void setElevation(int, float);
+ method public void setFloatValue(int, String!, float);
+ method public void setForceId(boolean);
+ method public void setGoneMargin(int, int, int);
+ method public void setGuidelineBegin(int, int);
+ method public void setGuidelineEnd(int, int);
+ method public void setGuidelinePercent(int, float);
+ method public void setHorizontalBias(int, float);
+ method public void setHorizontalChainStyle(int, int);
+ method public void setHorizontalWeight(int, float);
+ method public void setIntValue(int, String!, int);
+ method public void setLayoutWrapBehavior(int, int);
+ method public void setMargin(int, int, int);
+ method public void setReferencedIds(int, int...!);
+ method public void setRotation(int, float);
+ method public void setRotationX(int, float);
+ method public void setRotationY(int, float);
+ method public void setScaleX(int, float);
+ method public void setScaleY(int, float);
+ method public void setStateLabels(String!);
+ method public void setStateLabelsList(java.lang.String!...!);
+ method public void setStringValue(int, String!, String!);
+ method public void setTransformPivot(int, float, float);
+ method public void setTransformPivotX(int, float);
+ method public void setTransformPivotY(int, float);
+ method public void setTranslation(int, float, float);
+ method public void setTranslationX(int, float);
+ method public void setTranslationY(int, float);
+ method public void setTranslationZ(int, float);
+ method public void setValidateOnParse(boolean);
+ method public void setVerticalBias(int, float);
+ method public void setVerticalChainStyle(int, int);
+ method public void setVerticalWeight(int, float);
+ method public void setVisibility(int, int);
+ method public void setVisibilityMode(int, int);
+ method public void writeState(java.io.Writer!, androidx.constraintlayout.widget.ConstraintLayout!, int) throws java.io.IOException;
+ field public static final int BASELINE = 5; // 0x5
+ field public static final int BOTTOM = 4; // 0x4
+ field public static final int CHAIN_PACKED = 2; // 0x2
+ field public static final int CHAIN_SPREAD = 0; // 0x0
+ field public static final int CHAIN_SPREAD_INSIDE = 1; // 0x1
+ field public static final int CIRCLE_REFERENCE = 8; // 0x8
+ field public static final int END = 7; // 0x7
+ field public static final int GONE = 8; // 0x8
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int HORIZONTAL_GUIDELINE = 0; // 0x0
+ field public static final int INVISIBLE = 4; // 0x4
+ field public static final int LEFT = 1; // 0x1
+ field public static final int MATCH_CONSTRAINT = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_PERCENT = 2; // 0x2
+ field public static final int MATCH_CONSTRAINT_SPREAD = 0; // 0x0
+ field public static final int MATCH_CONSTRAINT_WRAP = 1; // 0x1
+ field public static final int PARENT_ID = 0; // 0x0
+ field public static final int RIGHT = 2; // 0x2
+ field public static final int ROTATE_LEFT_OF_PORTRATE = 4; // 0x4
+ field public static final int ROTATE_NONE = 0; // 0x0
+ field public static final int ROTATE_PORTRATE_OF_LEFT = 2; // 0x2
+ field public static final int ROTATE_PORTRATE_OF_RIGHT = 1; // 0x1
+ field public static final int ROTATE_RIGHT_OF_PORTRATE = 3; // 0x3
+ field public static final int START = 6; // 0x6
+ field public static final int TOP = 3; // 0x3
+ field public static final int UNSET = -1; // 0xffffffff
+ field public static final int VERTICAL = 1; // 0x1
+ field public static final int VERTICAL_GUIDELINE = 1; // 0x1
+ field public static final int VISIBILITY_MODE_IGNORE = 1; // 0x1
+ field public static final int VISIBILITY_MODE_NORMAL = 0; // 0x0
+ field public static final int VISIBLE = 0; // 0x0
+ field public static final int WRAP_CONTENT = -2; // 0xfffffffe
+ field public String! derivedState;
+ field public String! mIdString;
+ field public int mRotate;
+ }
+
+ public static class ConstraintSet.Constraint {
+ ctor public ConstraintSet.Constraint();
+ method public void applyDelta(androidx.constraintlayout.widget.ConstraintSet.Constraint!);
+ method public void applyTo(androidx.constraintlayout.widget.ConstraintLayout.LayoutParams!);
+ method public androidx.constraintlayout.widget.ConstraintSet.Constraint! clone();
+ method public void printDelta(String!);
+ field public final androidx.constraintlayout.widget.ConstraintSet.Layout! layout;
+ field public java.util.HashMap<java.lang.String!,androidx.constraintlayout.widget.ConstraintAttribute!>! mCustomConstraints;
+ field public final androidx.constraintlayout.widget.ConstraintSet.Motion! motion;
+ field public final androidx.constraintlayout.widget.ConstraintSet.PropertySet! propertySet;
+ field public final androidx.constraintlayout.widget.ConstraintSet.Transform! transform;
+ }
+
+ public static class ConstraintSet.Layout {
+ ctor public ConstraintSet.Layout();
+ method public void copyFrom(androidx.constraintlayout.widget.ConstraintSet.Layout!);
+ method public void dump(androidx.constraintlayout.motion.widget.MotionScene!, StringBuilder!);
+ field public static final int UNSET = -1; // 0xffffffff
+ field public static final int UNSET_GONE_MARGIN = -2147483648; // 0x80000000
+ field public int baselineMargin;
+ field public int baselineToBaseline;
+ field public int baselineToBottom;
+ field public int baselineToTop;
+ field public int bottomMargin;
+ field public int bottomToBottom;
+ field public int bottomToTop;
+ field public float circleAngle;
+ field public int circleConstraint;
+ field public int circleRadius;
+ field public boolean constrainedHeight;
+ field public boolean constrainedWidth;
+ field public String! dimensionRatio;
+ field public int editorAbsoluteX;
+ field public int editorAbsoluteY;
+ field public int endMargin;
+ field public int endToEnd;
+ field public int endToStart;
+ field public int goneBaselineMargin;
+ field public int goneBottomMargin;
+ field public int goneEndMargin;
+ field public int goneLeftMargin;
+ field public int goneRightMargin;
+ field public int goneStartMargin;
+ field public int goneTopMargin;
+ field public int guideBegin;
+ field public int guideEnd;
+ field public float guidePercent;
+ field public boolean guidelineUseRtl;
+ field public int heightDefault;
+ field public int heightMax;
+ field public int heightMin;
+ field public float heightPercent;
+ field public float horizontalBias;
+ field public int horizontalChainStyle;
+ field public float horizontalWeight;
+ field public int leftMargin;
+ field public int leftToLeft;
+ field public int leftToRight;
+ field public boolean mApply;
+ field public boolean mBarrierAllowsGoneWidgets;
+ field public int mBarrierDirection;
+ field public int mBarrierMargin;
+ field public String! mConstraintTag;
+ field public int mHeight;
+ field public int mHelperType;
+ field public boolean mIsGuideline;
+ field public boolean mOverride;
+ field public String! mReferenceIdString;
+ field public int[]! mReferenceIds;
+ field public int mWidth;
+ field public int mWrapBehavior;
+ field public int orientation;
+ field public int rightMargin;
+ field public int rightToLeft;
+ field public int rightToRight;
+ field public int startMargin;
+ field public int startToEnd;
+ field public int startToStart;
+ field public int topMargin;
+ field public int topToBottom;
+ field public int topToTop;
+ field public float verticalBias;
+ field public int verticalChainStyle;
+ field public float verticalWeight;
+ field public int widthDefault;
+ field public int widthMax;
+ field public int widthMin;
+ field public float widthPercent;
+ }
+
+ public static class ConstraintSet.Motion {
+ ctor public ConstraintSet.Motion();
+ method public void copyFrom(androidx.constraintlayout.widget.ConstraintSet.Motion!);
+ field public int mAnimateCircleAngleTo;
+ field public int mAnimateRelativeTo;
+ field public boolean mApply;
+ field public int mDrawPath;
+ field public float mMotionStagger;
+ field public int mPathMotionArc;
+ field public float mPathRotate;
+ field public int mPolarRelativeTo;
+ field public int mQuantizeInterpolatorID;
+ field public String! mQuantizeInterpolatorString;
+ field public int mQuantizeInterpolatorType;
+ field public float mQuantizeMotionPhase;
+ field public int mQuantizeMotionSteps;
+ field public String! mTransitionEasing;
+ }
+
+ public static class ConstraintSet.PropertySet {
+ ctor public ConstraintSet.PropertySet();
+ method public void copyFrom(androidx.constraintlayout.widget.ConstraintSet.PropertySet!);
+ field public float alpha;
+ field public boolean mApply;
+ field public float mProgress;
+ field public int mVisibilityMode;
+ field public int visibility;
+ }
+
+ public static class ConstraintSet.Transform {
+ ctor public ConstraintSet.Transform();
+ method public void copyFrom(androidx.constraintlayout.widget.ConstraintSet.Transform!);
+ field public boolean applyElevation;
+ field public float elevation;
+ field public boolean mApply;
+ field public float rotation;
+ field public float rotationX;
+ field public float rotationY;
+ field public float scaleX;
+ field public float scaleY;
+ field public int transformPivotTarget;
+ field public float transformPivotX;
+ field public float transformPivotY;
+ field public float translationX;
+ field public float translationY;
+ field public float translationZ;
+ }
+
+ public class Constraints extends android.view.ViewGroup {
+ ctor public Constraints(android.content.Context!);
+ ctor public Constraints(android.content.Context!, android.util.AttributeSet!);
+ ctor public Constraints(android.content.Context!, android.util.AttributeSet!, int);
+ method protected androidx.constraintlayout.widget.Constraints.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.constraintlayout.widget.Constraints.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
+ method public androidx.constraintlayout.widget.ConstraintSet! getConstraintSet();
+ field public static final String TAG = "Constraints";
+ }
+
+ public static class Constraints.LayoutParams extends androidx.constraintlayout.widget.ConstraintLayout.LayoutParams {
+ ctor public Constraints.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public Constraints.LayoutParams(androidx.constraintlayout.widget.Constraints.LayoutParams!);
+ ctor public Constraints.LayoutParams(int, int);
+ field public float alpha;
+ field public boolean applyElevation;
+ field public float elevation;
+ field public float rotation;
+ field public float rotationX;
+ field public float rotationY;
+ field public float scaleX;
+ field public float scaleY;
+ field public float transformPivotX;
+ field public float transformPivotY;
+ field public float translationX;
+ field public float translationY;
+ field public float translationZ;
+ }
+
+ public abstract class ConstraintsChangedListener {
+ ctor public ConstraintsChangedListener();
+ method public void postLayoutChange(int, int);
+ method public void preLayoutChange(int, int);
+ }
+
+ public class Group extends androidx.constraintlayout.widget.ConstraintHelper {
+ ctor public Group(android.content.Context!);
+ ctor public Group(android.content.Context!, android.util.AttributeSet!);
+ ctor public Group(android.content.Context!, android.util.AttributeSet!, int);
+ method public void onAttachedToWindow();
+ }
+
+ public class Guideline extends android.view.View {
+ ctor public Guideline(android.content.Context!);
+ ctor public Guideline(android.content.Context!, android.util.AttributeSet!);
+ ctor public Guideline(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public Guideline(android.content.Context!, android.util.AttributeSet!, int, int);
+ method public void setFilterRedundantCalls(boolean);
+ method public void setGuidelineBegin(int);
+ method public void setGuidelineEnd(int);
+ method public void setGuidelinePercent(float);
+ }
+
+ public class Placeholder extends android.view.View {
+ ctor public Placeholder(android.content.Context!);
+ ctor public Placeholder(android.content.Context!, android.util.AttributeSet!);
+ ctor public Placeholder(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public Placeholder(android.content.Context!, android.util.AttributeSet!, int, int);
+ method public android.view.View! getContent();
+ method public int getEmptyVisibility();
+ method public void onDraw(android.graphics.Canvas);
+ method public void setContentId(int);
+ method public void setEmptyVisibility(int);
+ method public void updatePostMeasure(androidx.constraintlayout.widget.ConstraintLayout!);
+ method public void updatePreLayout(androidx.constraintlayout.widget.ConstraintLayout!);
+ }
+
+ public class ReactiveGuide extends android.view.View implements androidx.constraintlayout.widget.SharedValues.SharedValuesListener {
+ ctor public ReactiveGuide(android.content.Context!);
+ ctor public ReactiveGuide(android.content.Context!, android.util.AttributeSet!);
+ ctor public ReactiveGuide(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public ReactiveGuide(android.content.Context!, android.util.AttributeSet!, int, int);
+ method public int getApplyToConstraintSetId();
+ method public int getAttributeId();
+ method public boolean isAnimatingChange();
+ method public void onNewValue(int, int, int);
+ method public void setAnimateChange(boolean);
+ method public void setApplyToConstraintSetId(int);
+ method public void setAttributeId(int);
+ method public void setGuidelineBegin(int);
+ method public void setGuidelineEnd(int);
+ method public void setGuidelinePercent(float);
+ }
+
+ public class SharedValues {
+ ctor public SharedValues();
+ method public void addListener(int, androidx.constraintlayout.widget.SharedValues.SharedValuesListener!);
+ method public void clearListeners();
+ method public void fireNewValue(int, int);
+ method public int getValue(int);
+ method public void removeListener(androidx.constraintlayout.widget.SharedValues.SharedValuesListener!);
+ method public void removeListener(int, androidx.constraintlayout.widget.SharedValues.SharedValuesListener!);
+ field public static final int UNSET = -1; // 0xffffffff
+ }
+
+ public static interface SharedValues.SharedValuesListener {
+ method public void onNewValue(int, int, int);
+ }
+
+ public class StateSet {
+ ctor public StateSet(android.content.Context!, org.xmlpull.v1.XmlPullParser!);
+ method public int convertToConstraintSet(int, int, float, float);
+ method public boolean needsToChange(int, float, float);
+ method public void setOnConstraintsChanged(androidx.constraintlayout.widget.ConstraintsChangedListener!);
+ method public int stateGetConstraintID(int, int, int);
+ method public int updateConstraints(int, int, float, float);
+ field public static final String TAG = "ConstraintLayoutStates";
+ }
+
+ public abstract class VirtualLayout extends androidx.constraintlayout.widget.ConstraintHelper {
+ ctor public VirtualLayout(android.content.Context!);
+ ctor public VirtualLayout(android.content.Context!, android.util.AttributeSet!);
+ ctor public VirtualLayout(android.content.Context!, android.util.AttributeSet!, int);
+ method public void onAttachedToWindow();
+ method public void onMeasure(androidx.constraintlayout.core.widgets.VirtualLayout!, int, int);
+ }
+
+}
+
diff --git a/core/core-splashscreen/src/androidTest/java/androidx/core/splashscreen/test/SplashscreenParametrizedTest.kt b/core/core-splashscreen/src/androidTest/java/androidx/core/splashscreen/test/SplashscreenParametrizedTest.kt
index ec8d536..74c7f09 100644
--- a/core/core-splashscreen/src/androidTest/java/androidx/core/splashscreen/test/SplashscreenParametrizedTest.kt
+++ b/core/core-splashscreen/src/androidTest/java/androidx/core/splashscreen/test/SplashscreenParametrizedTest.kt
@@ -19,6 +19,8 @@
import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle
+import android.util.Base64
+import android.util.Log
import android.view.View
import android.view.ViewTreeObserver
import androidx.core.splashscreen.SplashScreenViewProvider
@@ -28,6 +30,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.screenshot.matchers.MSSIMMatcher
import androidx.test.uiautomator.UiDevice
+import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
@@ -63,6 +66,8 @@
arrayOf("AppCompat", SplashScreenAppCompatTestActivity::class)
)
}
+
+ const val TAG = "SplashscreenParameterizedTest"
}
@Before
@@ -310,6 +315,12 @@
)
if (!matcher.matches) {
+ // Serialize the screenshots and output them through Logcat so as to gather more details
+ // for debugging.
+ logLongMessage(Log::e, TAG, "before", beforeScreenshot.toBase64String())
+ logLongMessage(Log::e, TAG, "after", afterScreenshot.toBase64String())
+ matcher.diff?.let { logLongMessage(Log::e, TAG, "diff", it.toBase64String()) }
+
val bundle = Bundle()
val diff = matcher.diff?.writeToDevice("diff.png")
bundle.putString("splashscreen_diff", diff?.absolutePath)
@@ -330,6 +341,50 @@
}
}
+ /**
+ * A log message has a maximum of 4096 bytes, where date / time, tag, process, etc. included.
+ *
+ * Therefore, we should chunk a large message into some smaller ones.
+ */
+ private fun logLongMessage(
+ logger: (tag: String, msg: String) -> Int,
+ tag: String,
+ title: String,
+ msg: String
+ ) {
+ val chunks = msg.chunked(4000)
+ logger(tag, "$title ${chunks.size}")
+
+ for ((i, chunk) in chunks.withIndex()) {
+ logger(tag, title + " $i/${chunks.size} " + chunk)
+ }
+ }
+
+ /**
+ * Serialize a bitmap into a string in Base64 encoding so that we could output it through logs
+ * when comparisons fail.
+ */
+ private fun Bitmap.toBase64String(): String {
+ val scaledBitmap =
+ Bitmap.createScaledBitmap(
+ this,
+ // Reduce the size of the bitmap
+ width * 3 shr 2,
+ height * 3 shr 2,
+ false
+ )
+ val outputStream = ByteArrayOutputStream()
+ scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
+
+ val bytes = outputStream.toByteArray()
+ val str =
+ Base64.encodeToString(
+ bytes,
+ Base64.NO_WRAP // Not to wrap here as we are going to wrap on our own later
+ )
+ return str
+ }
+
private fun Bitmap.writeToDevice(name: String): File {
return writeToDevice(
{ compress(Bitmap.CompressFormat.PNG, 0 /*ignored for png*/, it) },
diff --git a/core/core-telecom/api/current.txt b/core/core-telecom/api/current.txt
index 9cccfad..c94b261 100644
--- a/core/core-telecom/api/current.txt
+++ b/core/core-telecom/api/current.txt
@@ -57,8 +57,9 @@
property public abstract kotlinx.coroutines.flow.Flow<java.lang.Boolean> isMuted;
}
- @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CallEndpointCompat {
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CallEndpointCompat implements java.lang.Comparable<androidx.core.telecom.CallEndpointCompat> {
ctor public CallEndpointCompat(CharSequence name, int type, android.os.ParcelUuid identifier);
+ method public int compareTo(androidx.core.telecom.CallEndpointCompat other);
method public android.os.ParcelUuid getIdentifier();
method public CharSequence getName();
method public int getType();
diff --git a/core/core-telecom/api/restricted_current.txt b/core/core-telecom/api/restricted_current.txt
index 9cccfad..c94b261 100644
--- a/core/core-telecom/api/restricted_current.txt
+++ b/core/core-telecom/api/restricted_current.txt
@@ -57,8 +57,9 @@
property public abstract kotlinx.coroutines.flow.Flow<java.lang.Boolean> isMuted;
}
- @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CallEndpointCompat {
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CallEndpointCompat implements java.lang.Comparable<androidx.core.telecom.CallEndpointCompat> {
ctor public CallEndpointCompat(CharSequence name, int type, android.os.ParcelUuid identifier);
+ method public int compareTo(androidx.core.telecom.CallEndpointCompat other);
method public android.os.ParcelUuid getIdentifier();
method public CharSequence getName();
method public int getType();
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallEndpointCompatTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallEndpointCompatTest.kt
index 3a12399..5ec499d 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallEndpointCompatTest.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallEndpointCompatTest.kt
@@ -18,14 +18,13 @@
import android.media.AudioDeviceInfo
import android.os.Build.VERSION_CODES
-import android.os.ParcelUuid
import android.telecom.CallAudioState
import androidx.annotation.RequiresApi
import androidx.core.telecom.CallEndpointCompat
import androidx.core.telecom.internal.utils.EndpointUtils
import androidx.core.telecom.internal.utils.EndpointUtils.Companion.remapAudioDeviceTypeToCallEndpointType
+import androidx.core.telecom.test.utils.TestUtils
import androidx.test.filters.SdkSuppress
-import java.util.UUID
import org.junit.Assert.assertEquals
import org.junit.Test
@@ -36,7 +35,7 @@
fun testCallEndpointConstructor() {
val name = "Endpoint"
val type = CallEndpointCompat.TYPE_EARPIECE
- val identifier = ParcelUuid.fromString(UUID.randomUUID().toString())
+ val identifier = TestUtils.generateRandomUuid()
val endpoint = CallEndpointCompat(name, type, identifier)
assertEquals(name, endpoint.name)
assertEquals(type, endpoint.type)
@@ -170,4 +169,64 @@
remapAudioDeviceTypeToCallEndpointType(AudioDeviceInfo.TYPE_BLE_BROADCAST)
)
}
+
+ @SdkSuppress(minSdkVersion = VERSION_CODES.O)
+ @Test
+ fun testDefaultSort() {
+ val highestPriorityEndpoint = CallEndpointCompat("F", CallEndpointCompat.TYPE_WIRED_HEADSET)
+ val second = CallEndpointCompat("E", CallEndpointCompat.TYPE_BLUETOOTH)
+ val third = CallEndpointCompat("D", CallEndpointCompat.TYPE_SPEAKER)
+ val fourth = CallEndpointCompat("C", CallEndpointCompat.TYPE_EARPIECE)
+ val fifth = CallEndpointCompat("B", CallEndpointCompat.TYPE_STREAMING)
+ val lowestPriorityEndpoint = CallEndpointCompat("A", CallEndpointCompat.TYPE_UNKNOWN)
+
+ val endpoints =
+ mutableListOf(
+ lowestPriorityEndpoint,
+ fourth,
+ second,
+ fifth,
+ third,
+ highestPriorityEndpoint
+ )
+
+ endpoints.sort()
+
+ assertEquals(highestPriorityEndpoint, endpoints[0])
+ assertEquals(second, endpoints[1])
+ assertEquals(third, endpoints[2])
+ assertEquals(fourth, endpoints[3])
+ assertEquals(fifth, endpoints[4])
+ assertEquals(lowestPriorityEndpoint, endpoints[5])
+ }
+
+ @SdkSuppress(minSdkVersion = VERSION_CODES.O)
+ @Test
+ fun testDefaultSortWithDuplicateTypes() {
+ val highestPriorityEndpoint = CallEndpointCompat("A", CallEndpointCompat.TYPE_BLUETOOTH)
+ val second = CallEndpointCompat("B", CallEndpointCompat.TYPE_BLUETOOTH)
+ val third = CallEndpointCompat("C", CallEndpointCompat.TYPE_BLUETOOTH)
+ val fourth = CallEndpointCompat("D", CallEndpointCompat.TYPE_BLUETOOTH)
+ val fifth = CallEndpointCompat("E", CallEndpointCompat.TYPE_BLUETOOTH)
+ val lowestPriorityEndpoint = CallEndpointCompat("F", CallEndpointCompat.TYPE_BLUETOOTH)
+
+ val endpoints =
+ mutableListOf(
+ lowestPriorityEndpoint,
+ fourth,
+ second,
+ fifth,
+ third,
+ highestPriorityEndpoint
+ )
+
+ endpoints.sort()
+
+ assertEquals(highestPriorityEndpoint, endpoints[0])
+ assertEquals(second, endpoints[1])
+ assertEquals(third, endpoints[2])
+ assertEquals(fourth, endpoints[3])
+ assertEquals(fifth, endpoints[4])
+ assertEquals(lowestPriorityEndpoint, endpoints[5])
+ }
}
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
index 7548186..79b9d8f 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
@@ -22,6 +22,7 @@
import android.os.Build
import android.os.Build.VERSION_CODES
import android.os.Bundle
+import android.os.ParcelUuid
import android.os.UserHandle
import android.os.UserManager
import android.telecom.Call
@@ -38,6 +39,7 @@
import androidx.core.telecom.util.ExperimentalAppActions
import androidx.test.platform.app.InstrumentationRegistry
import java.io.FileInputStream
+import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.delay
@@ -291,6 +293,10 @@
Log.i(LOG_TAG, "defaultDialer=[${getDefaultDialer()}]")
}
+ fun generateRandomUuid(): ParcelUuid {
+ return ParcelUuid.fromString(UUID.randomUUID().toString())
+ }
+
@OptIn(ExperimentalAppActions::class)
@Suppress("deprecation")
internal suspend fun waitOnInCallServiceToReachXCalls(
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/CallEndpointCompat.kt b/core/core-telecom/src/main/java/androidx/core/telecom/CallEndpointCompat.kt
index c1ccf20..d884c4d 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/CallEndpointCompat.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/CallEndpointCompat.kt
@@ -39,7 +39,7 @@
public val name: CharSequence,
public val type: Int,
public val identifier: ParcelUuid
-) {
+) : Comparable<CallEndpointCompat> {
internal var mMackAddress: String = "-1"
override fun toString(): String {
@@ -49,6 +49,29 @@
"identifier=[$identifier])"
}
+ /**
+ * Compares this [CallEndpointCompat] to the other [CallEndpointCompat] for order. Returns a
+ * positive number if this type rank is greater than the other value. Returns a negative number
+ * if this type rank is less than the other value. Sort the CallEndpoint by type. Ranking them
+ * by:
+ * 1. TYPE_WIRED_HEADSET
+ * 2. TYPE_BLUETOOTH
+ * 3. TYPE_SPEAKER
+ * 4. TYPE_EARPIECE
+ * 5. TYPE_STREAMING
+ * 6. TYPE_UNKNOWN If two endpoints have the same type, the name is compared to determine the
+ * value.
+ */
+ override fun compareTo(other: CallEndpointCompat): Int {
+ // sort by type
+ val res = this.getTypeRank().compareTo(other.getTypeRank())
+ if (res != 0) {
+ return res
+ }
+ // break ties using alphabetic order
+ return this.name.toString().compareTo(other.name.toString())
+ }
+
override fun equals(other: Any?): Boolean {
return other is CallEndpointCompat &&
name == other.name &&
@@ -112,4 +135,15 @@
internal fun isBluetoothType(): Boolean {
return type == TYPE_BLUETOOTH
}
+
+ private fun getTypeRank(): Int {
+ return when (this.type) {
+ TYPE_WIRED_HEADSET -> return 0
+ TYPE_BLUETOOTH -> return 1
+ TYPE_SPEAKER -> return 2
+ TYPE_EARPIECE -> return 3
+ TYPE_STREAMING -> return 4
+ else -> 5
+ }
+ }
}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt b/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
index ee7d73b..71ef177 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
@@ -361,6 +361,10 @@
* cancel() from the same [kotlinx.coroutines.CoroutineScope] the [callbackFlow] is collecting
* in.
*
+ * Note: The endpoints emitted will be sorted by the [CallEndpointCompat.type] . See
+ * [CallEndpointCompat.compareTo] for the ordering. The first element in the list will be the
+ * recommended call endpoint to default to for the user.
+ *
* @return a flow of [CallEndpointCompat]s that can be used for a new call session
*/
public fun getAvailableStartingCallEndpoints(): Flow<List<CallEndpointCompat>> = callbackFlow {
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt
index aaebe36..e96ccbd 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt
@@ -157,7 +157,7 @@
override fun onAvailableCallEndpointsChanged(endpoints: List<CallEndpoint>) {
// due to the [CallsManager#getAvailableStartingCallEndpoints] API, endpoints the client
// has can be different from the ones coming from the platform. Hence, a remapping is needed
- mAvailableEndpoints = endpoints.map { toRemappedCallEndpointCompat(it) }
+ mAvailableEndpoints = endpoints.map { toRemappedCallEndpointCompat(it) }.sorted()
// send the current call endpoints out to the client
callChannels.availableEndpointChannel.trySend(mAvailableEndpoints).getOrThrow()
Log.i(TAG, "onAvailableCallEndpointsChanged: endpoints=[$endpoints]")
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
index ec7f0c6..64e199d 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
@@ -142,7 +142,7 @@
private fun setAvailableCallEndpoints(state: CallAudioState) {
val availableEndpoints =
- toCallEndpointsCompat(state).map { toRemappedCallEndpointCompat(it) }
+ toCallEndpointsCompat(state).map { toRemappedCallEndpointCompat(it) }.sorted()
mAvailableCallEndpoints = availableEndpoints
callChannels.availableEndpointChannel.trySend(availableEndpoints).getOrThrow()
}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/PreCallEndpoints.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/PreCallEndpoints.kt
index 7f0b519..0371d2e 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/PreCallEndpoints.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/PreCallEndpoints.kt
@@ -127,6 +127,8 @@
}
private fun updateClient() {
+ // Sort by endpoint type. The first element has the highest priority!
+ mCurrentDevices.sort()
mSendChannel.trySend(mCurrentDevices)
}
}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt
index 4b5cdbd..54b3f6d 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt
@@ -61,6 +61,8 @@
}
omittedDevices.append("]")
Log.i(TAG, omittedDevices.toString())
+ // Sort by endpoint type. The first element has the highest priority!
+ endpoints.sort()
return endpoints
}
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-af/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-af/strings.xml
index e752dd2..e5c55e0 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-af/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Oorstuk"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Kabelkopstuk"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Luidspreker"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-am/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-am/strings.xml
index e752dd2..66301fb 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-am/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ማዳመጫ"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"ባለ ገመድ ማዳመጫ"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"ድምፅ ማውጫ"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-ar/strings.xml b/core/core-telecom/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..8963293
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ar/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"سماعة أذن"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"سماعة رأس سلكية"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"مكبِّر صوت"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-as/strings.xml b/core/core-telecom/src/main/res/values-as/strings.xml
new file mode 100644
index 0000000..697b692
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-as/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ইয়েৰপিচ"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"তাঁৰযুক্ত হেডছেট"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"স্পীকাৰ"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-az/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-az/strings.xml
index e752dd2..e62a4d0 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-az/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Qulaqlıq"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Simli qulaqlıq"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Dinamik"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-b+sr+Latn/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-b+sr+Latn/strings.xml
index e752dd2..6afc996 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-b+sr+Latn/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Slušalica"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Žičane slušalice"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Zvučnik"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-be/strings.xml b/core/core-telecom/src/main/res/values-be/strings.xml
new file mode 100644
index 0000000..cb6122ec
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-be/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Дынамік"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Правадная гарнітура"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Гучная сувязь"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-bg/strings.xml b/core/core-telecom/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000..91d381b
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-bg/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Слушалка"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Слушалки с кабел"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Високоговорител"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-bn/strings.xml b/core/core-telecom/src/main/res/values-bn/strings.xml
new file mode 100644
index 0000000..5acdea3
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-bn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ইয়ারপিস"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"ওয়্যার্ড হেডসেট"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"স্পিকার"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-bs/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-bs/strings.xml
index e752dd2..6afc996 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-bs/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Slušalica"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Žičane slušalice"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Zvučnik"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-ca/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-ca/strings.xml
index e752dd2..4b8b784 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-ca/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Auricular"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Auriculars amb cable"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Altaveu"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-cs/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-cs/strings.xml
index e752dd2..9bafe17 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-cs/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Sluchátko"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Kabelová náhlavní souprava"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Reproduktor"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-da/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-da/strings.xml
index e752dd2..257ec4e 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-da/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Højttaler"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Headset med ledning"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Højttaler"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-de/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-de/strings.xml
index e752dd2..0ce2c07 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-de/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Kopfhörer"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Kabelgebundenes Headset"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Lautsprecher"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-el/strings.xml b/core/core-telecom/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000..83770ab
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-el/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Ακουστικό τηλεφώνου"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Ενσύρματα ακουστικά"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Ηχείο"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-en-rAU/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-en-rAU/strings.xml
index e752dd2..ae0ae39 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-en-rAU/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Earpiece"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Wired headset"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Speaker"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-en-rCA/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-en-rCA/strings.xml
index e752dd2..ae0ae39 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-en-rCA/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Earpiece"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Wired headset"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Speaker"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-en-rGB/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-en-rGB/strings.xml
index e752dd2..ae0ae39 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-en-rGB/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Earpiece"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Wired headset"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Speaker"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-en-rIN/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-en-rIN/strings.xml
index e752dd2..ae0ae39 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-en-rIN/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Earpiece"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Wired headset"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Speaker"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-en-rXC/strings.xml b/core/core-telecom/src/main/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..db0d654
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-en-rXC/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Earpiece"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Wired headset"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Speaker"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-es-rUS/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-es-rUS/strings.xml
index e752dd2..1b87fa9 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-es-rUS/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Auricular"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Auriculares con cable"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Bocina"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-es/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-es/strings.xml
index e752dd2..5af28da 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-es/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Auricular"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Auriculares con cable"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Altavoz"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-et/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-et/strings.xml
index e752dd2..3d2cc0c 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-et/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Kuular"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Juhtmega peakomplekt"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Kõlar"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-eu/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-eu/strings.xml
index e752dd2..16cc5e9 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-eu/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Aurikularra"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Entzungailu kableduna"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Bozgorailua"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-fa/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-fa/strings.xml
index e752dd2..e159d7c 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-fa/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"گوشی"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"هدست سیمی"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"بلندگو"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-fi/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-fi/strings.xml
index e752dd2..1772c83 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-fi/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Kaiutin"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Langallinen kuulokemikrofoni"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Kaiutin"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-fr-rCA/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-fr-rCA/strings.xml
index e752dd2..33d0ca4 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-fr-rCA/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Écouteur"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Écouteurs filaires"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Haut-parleur"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-fr/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-fr/strings.xml
index e752dd2..efba6ec 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-fr/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Écouteur"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Casque filaire"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Haut-parleur"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-gl/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-gl/strings.xml
index e752dd2..73f3cf5 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-gl/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Auricular"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Auriculares con cable"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Altofalante"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-gu/strings.xml b/core/core-telecom/src/main/res/values-gu/strings.xml
new file mode 100644
index 0000000..eb7a9ee
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-gu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ઇયરપીસ"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"વાયર્ડ હૅડસેટ"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"સ્પીકર"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-hi/strings.xml b/core/core-telecom/src/main/res/values-hi/strings.xml
new file mode 100644
index 0000000..3a847ac
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-hi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ईयरपीस"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"वायर वाला हेडसेट"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"स्पीकर"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-hr/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-hr/strings.xml
index e752dd2..a812b93 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-hr/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Zvučnik"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Žičane slušalice"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Zvučnik"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-hu/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-hu/strings.xml
index e752dd2..1175ec2 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-hu/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Fülhallgató"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Vezetékes headset"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Hangszóró"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-hy/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-hy/strings.xml
index e752dd2..0f1a0d2 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-hy/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Լսափող"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Լարով ականջակալ"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Բարձրախոս"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-in/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-in/strings.xml
index e752dd2..8e053cc 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-in/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Earpiece"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Headset berkabel"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Speaker"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-is/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-is/strings.xml
index e752dd2..c0befd0 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-is/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Hátalari"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Höfuðtól með snúru"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Hátalari"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-it/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-it/strings.xml
index e752dd2..1ffa5ad 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-it/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Auricolare"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Cuffie con cavo"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Speaker"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-iw/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-iw/strings.xml
index e752dd2..9ec7ee9 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-iw/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"אוזניה"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"אוזניות חוטיות"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"רמקול"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-ja/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-ja/strings.xml
index e752dd2..fecacc1 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-ja/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"受話口"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"有線ヘッドセット"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"スピーカー"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-ka/strings.xml b/core/core-telecom/src/main/res/values-ka/strings.xml
new file mode 100644
index 0000000..d93c507
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ka/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ყურმილი"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"სადენიანი ყურსაცვამი"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"დინამიკი"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-kk/strings.xml b/core/core-telecom/src/main/res/values-kk/strings.xml
new file mode 100644
index 0000000..6867ece
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-kk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Телефон динамигі"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Сымды гарнитура"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Динамик"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-km/strings.xml b/core/core-telecom/src/main/res/values-km/strings.xml
new file mode 100644
index 0000000..0fc75bb
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-km/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ឧបករណ៍ស្ដាប់សំឡេង"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"កាសមានខ្សែ"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"ឧបករណ៍បំពងសំឡេង"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-kn/strings.xml b/core/core-telecom/src/main/res/values-kn/strings.xml
new file mode 100644
index 0000000..4b006c3
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-kn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ಇಯರ್ಪೀಸ್"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"ವೈಯರ್ಡ್ ಹೆಡ್ಸೆಟ್"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"ಸ್ಪೀಕರ್"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-ko/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-ko/strings.xml
index e752dd2..6dd6a7d 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-ko/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"스피커"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"유선 헤드셋"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"스피커"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-ky/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-ky/strings.xml
index e752dd2..4de5b55 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-ky/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Кулакчын"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Зымдуу гарнитура"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Динамик"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-lo/strings.xml b/core/core-telecom/src/main/res/values-lo/strings.xml
new file mode 100644
index 0000000..fae8f3f
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-lo/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ຫູຟັງ"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"ຊຸດຫູຟັງແບບມີສາຍ"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"ລຳໂພງ"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-lt/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-lt/strings.xml
index e752dd2..ab5b490 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-lt/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Garsiakalbis prie ausies"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Laidinės ausinės"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Garsiakalbis"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-lv/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-lv/strings.xml
index e752dd2..dbbe825 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-lv/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Auss skaļrunis"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Vadu austiņas"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Skaļrunis"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-mk/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-mk/strings.xml
index e752dd2..73a43de 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-mk/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Слушалка"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Жичени слушалки"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Звучник"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-ml/strings.xml b/core/core-telecom/src/main/res/values-ml/strings.xml
new file mode 100644
index 0000000..7efb254
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ml/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ഇയർഫോൺ"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"വയേർഡ് ഹെഡ്സെറ്റ്"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"സ്പീക്കർ"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-mn/strings.xml b/core/core-telecom/src/main/res/values-mn/strings.xml
new file mode 100644
index 0000000..3f929e9
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-mn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Чихний спикер"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Утастай чихэвч"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Чанга яригч"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-mr/strings.xml b/core/core-telecom/src/main/res/values-mr/strings.xml
new file mode 100644
index 0000000..343c6c2
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-mr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"इअरपीस"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"वायर्ड हेडसेट"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"स्पीकर"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-ms/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-ms/strings.xml
index e752dd2..1ffead0 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-ms/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Alat dengar"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Set kepala berwayar"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Pembesar suara"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-my/strings.xml b/core/core-telecom/src/main/res/values-my/strings.xml
new file mode 100644
index 0000000..a62aea0
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-my/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"တယ်လီဖုန်းနားခွက်"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"ကြိုးတပ် မိုက်ခွက်ပါနားကြပ်"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"စပီကာ"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-nb/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-nb/strings.xml
index e752dd2..387722e 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-nb/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Ørehøyttaler"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Hodetelefoner med ledning"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Høyttaler"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-ne/strings.xml b/core/core-telecom/src/main/res/values-ne/strings.xml
new file mode 100644
index 0000000..30d613e
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ne/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"इयरपिस"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"तारसहितको हेडसेट"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"स्पिकर"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-nl/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-nl/strings.xml
index e752dd2..c496439 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-nl/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Oortelefoon"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Bedrade headset"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Speaker"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-or/strings.xml b/core/core-telecom/src/main/res/values-or/strings.xml
new file mode 100644
index 0000000..2a480c7
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-or/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ଇୟରପିସ"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"ତାରଯୁକ୍ତ ହେଡସେଟ"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"ସ୍ପିକର"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-pa/strings.xml b/core/core-telecom/src/main/res/values-pa/strings.xml
new file mode 100644
index 0000000..ebc4300
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-pa/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ਈਅਰਪੀਸ"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"ਤਾਰ ਵਾਲਾ ਹੈੱਡਸੈੱਟ"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"ਸਪੀਕਰ"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-pl/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-pl/strings.xml
index e752dd2..768b794 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-pl/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Słuchawka"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Przewodowy zestaw słuchawkowy"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Głośnik"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-pt-rBR/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-pt-rBR/strings.xml
index e752dd2..de52295 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-pt-rBR/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Minifone de ouvido"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Fone de ouvido com fio"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Alto-falante"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-pt-rPT/strings.xml b/core/core-telecom/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..ee657a4
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Auricular"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Auscultadores com microfone integrado com fios"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Altifalante"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-pt/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-pt/strings.xml
index e752dd2..de52295 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-pt/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Minifone de ouvido"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Fone de ouvido com fio"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Alto-falante"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-ro/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-ro/strings.xml
index e752dd2..a1b63da 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-ro/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Cască"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Set de căști-microfon cu fir"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Difuzor"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-ru/strings.xml b/core/core-telecom/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..9f1c287
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ru/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Наушник"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Проводная гарнитура"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Колонка"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-si/strings.xml b/core/core-telecom/src/main/res/values-si/strings.xml
new file mode 100644
index 0000000..748d5c2
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-si/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"සවන් කඩ"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"රැහැන්ගත කළ හෙඩ්සෙට්"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"ස්පීකරය"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-sk/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-sk/strings.xml
index e752dd2..b6075f4 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-sk/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Slúchadlo"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Káblové slúchadlá s mikrofónom"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Reproduktor"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-sl/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-sl/strings.xml
index e752dd2..5ca1886 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-sl/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Slušalka"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Žične slušalke z mikrofonom"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Zvočnik"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-sq/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-sq/strings.xml
index e752dd2..c932e8d 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-sq/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Receptor"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Kufje me tel"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Altoparlant"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-sr/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-sr/strings.xml
index e752dd2..199a682 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-sr/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Слушалица"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Жичане слушалице"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Звучник"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-sv/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-sv/strings.xml
index e752dd2..aec1523 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-sv/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Lur"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Kabelanslutet headset"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Högtalare"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-sw/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-sw/strings.xml
index e752dd2..af27136d 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-sw/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Spika ya sikioni"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Vifaa vya sauti vyenye waya"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Spika"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-ta/strings.xml b/core/core-telecom/src/main/res/values-ta/strings.xml
new file mode 100644
index 0000000..b3678bb
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ta/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ஒலி கேட்கும் பகுதி"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"வயர் ஹெட்செட்"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"ஸ்பீக்கர்"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-te/strings.xml b/core/core-telecom/src/main/res/values-te/strings.xml
new file mode 100644
index 0000000..e387597
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-te/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ఇయర్పీస్"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"వైర్ ఉన్న హెడ్సెట్"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"స్పీకర్"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-th/strings.xml b/core/core-telecom/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000..103ceb1
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-th/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"หูฟังโทรศัพท์"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"ชุดหูฟังแบบมีสาย"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"ลำโพง"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-tl/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-tl/strings.xml
index e752dd2..50ac56c 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-tl/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Earpiece"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Wired na headset"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Speaker"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-tr/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-tr/strings.xml
index e752dd2..600c6d2 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-tr/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Kulaklık"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Kablolu mikrofonlu kulaklık"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Hoparlör"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-uk/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-uk/strings.xml
index e752dd2..d8580f9 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-uk/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Динамік"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Дротова гарнітура"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Колонка"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-ur/strings.xml
similarity index 60%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-ur/strings.xml
index e752dd2..d577f0b 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-ur/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"ایئر پیس"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"تار والا ہیڈ سیٹ"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"اسپیکر"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-uz/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-uz/strings.xml
index e752dd2..679f5dd 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-uz/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Quloq karnaychasi"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Simli garnitura"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Karnay"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-vi/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-vi/strings.xml
index e752dd2..a4c82f9 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-vi/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Loa tai nghe"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Tai nghe có dây"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Loa ngoài"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-zh-rCN/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-zh-rCN/strings.xml
index e752dd2..0d4efb7 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-zh-rCN/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"手机听筒"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"有线头戴式耳机"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"扬声器"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-zh-rHK/strings.xml
similarity index 62%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-zh-rHK/strings.xml
index e752dd2..4e99591 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-zh-rHK/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"聽筒"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"有線耳機"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"喇叭"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-zh-rTW/strings.xml
similarity index 62%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-zh-rTW/strings.xml
index e752dd2..4c586c7 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-zh-rTW/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"耳機"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"有線耳機"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"喇叭"</string>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/core/core-telecom/src/main/res/values-zu/strings.xml
similarity index 61%
copy from pdf/pdf-viewer/src/main/res/values/styles.xml
copy to core/core-telecom/src/main/res/values-zu/strings.xml
index e752dd2..7aab3c5 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/core/core-telecom/src/main/res/values-zu/strings.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +13,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
-</resources>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="callendpoint_name_earpiece" msgid="4519059065203201851">"Isipikha sendlebe"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Iheadset enentambo"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Isipikha"</string>
+</resources>
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 8d7d895..2c49d7e 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1850,7 +1850,7 @@
package androidx.core.os {
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public abstract sealed class BufferFillPolicy {
+ @RequiresApi(api=35) public abstract sealed class BufferFillPolicy {
field public static final androidx.core.os.BufferFillPolicy.Companion Companion;
field public static final androidx.core.os.BufferFillPolicy DISCARD;
field public static final androidx.core.os.BufferFillPolicy RING_BUFFER;
@@ -1926,7 +1926,7 @@
method public static boolean postDelayed(android.os.Handler, Runnable, Object?, long);
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class HeapProfileRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.HeapProfileRequestBuilder> {
+ @RequiresApi(api=35) public final class HeapProfileRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.HeapProfileRequestBuilder> {
ctor public HeapProfileRequestBuilder();
method public androidx.core.os.HeapProfileRequestBuilder setBufferSizeKb(int bufferSizeKb);
method public androidx.core.os.HeapProfileRequestBuilder setDurationMs(int durationMs);
@@ -1934,7 +1934,7 @@
method public androidx.core.os.HeapProfileRequestBuilder setTrackJavaAllocations(boolean traceJavaAllocations);
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class JavaHeapDumpRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.JavaHeapDumpRequestBuilder> {
+ @RequiresApi(api=35) public final class JavaHeapDumpRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.JavaHeapDumpRequestBuilder> {
ctor public JavaHeapDumpRequestBuilder();
method public androidx.core.os.JavaHeapDumpRequestBuilder setBufferSizeKb(int bufferSizeKb);
}
@@ -1998,13 +1998,13 @@
}
public final class Profiling {
- method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static kotlinx.coroutines.flow.Flow<android.os.ProfilingResult> registerForAllProfilingResults(android.content.Context context);
- method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void registerForAllProfilingResults(android.content.Context context, java.util.concurrent.Executor executor, java.util.function.Consumer<android.os.ProfilingResult> listener);
- method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void requestProfiling(android.content.Context context, androidx.core.os.ProfilingRequest profilingRequest, java.util.concurrent.Executor? executor, java.util.function.Consumer<android.os.ProfilingResult>? listener);
- method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void unregisterForAllProfilingResults(android.content.Context context, java.util.function.Consumer<android.os.ProfilingResult> listener);
+ method @RequiresApi(api=35) public static kotlinx.coroutines.flow.Flow<android.os.ProfilingResult> registerForAllProfilingResults(android.content.Context context);
+ method @RequiresApi(api=35) public static void registerForAllProfilingResults(android.content.Context context, java.util.concurrent.Executor executor, java.util.function.Consumer<android.os.ProfilingResult> listener);
+ method @RequiresApi(api=35) public static void requestProfiling(android.content.Context context, androidx.core.os.ProfilingRequest profilingRequest, java.util.concurrent.Executor? executor, java.util.function.Consumer<android.os.ProfilingResult>? listener);
+ method @RequiresApi(api=35) public static void unregisterForAllProfilingResults(android.content.Context context, java.util.function.Consumer<android.os.ProfilingResult> listener);
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class ProfilingRequest {
+ @RequiresApi(api=35) public final class ProfilingRequest {
method public android.os.CancellationSignal? getCancellationSignal();
method public android.os.Bundle getParams();
method public int getProfilingType();
@@ -2015,20 +2015,20 @@
property public final String? tag;
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public abstract class ProfilingRequestBuilder<T extends androidx.core.os.ProfilingRequestBuilder<T>> {
+ @RequiresApi(api=35) public abstract class ProfilingRequestBuilder<T extends androidx.core.os.ProfilingRequestBuilder<T>> {
method public final androidx.core.os.ProfilingRequest build();
method public final T setCancellationSignal(android.os.CancellationSignal cancellationSignal);
method public final T setTag(String tag);
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class StackSamplingRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.StackSamplingRequestBuilder> {
+ @RequiresApi(api=35) public final class StackSamplingRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.StackSamplingRequestBuilder> {
ctor public StackSamplingRequestBuilder();
method public androidx.core.os.StackSamplingRequestBuilder setBufferSizeKb(int bufferSizeKb);
method public androidx.core.os.StackSamplingRequestBuilder setDurationMs(int durationMs);
method public androidx.core.os.StackSamplingRequestBuilder setSamplingFrequencyHz(int samplingFrequencyHz);
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class SystemTraceRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.SystemTraceRequestBuilder> {
+ @RequiresApi(api=35) public final class SystemTraceRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.SystemTraceRequestBuilder> {
ctor public SystemTraceRequestBuilder();
method public androidx.core.os.SystemTraceRequestBuilder setBufferFillPolicy(androidx.core.os.BufferFillPolicy bufferFillPolicy);
method public androidx.core.os.SystemTraceRequestBuilder setBufferSizeKb(int bufferSizeKb);
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index c60ec0c..4f16f2d 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -2243,7 +2243,7 @@
package androidx.core.os {
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public abstract sealed class BufferFillPolicy {
+ @RequiresApi(api=35) public abstract sealed class BufferFillPolicy {
field public static final androidx.core.os.BufferFillPolicy.Companion Companion;
field public static final androidx.core.os.BufferFillPolicy DISCARD;
field public static final androidx.core.os.BufferFillPolicy RING_BUFFER;
@@ -2319,7 +2319,7 @@
method public static boolean postDelayed(android.os.Handler, Runnable, Object?, long);
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class HeapProfileRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.HeapProfileRequestBuilder> {
+ @RequiresApi(api=35) public final class HeapProfileRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.HeapProfileRequestBuilder> {
ctor public HeapProfileRequestBuilder();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected android.os.Bundle getParams();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected int getProfilingType();
@@ -2330,7 +2330,7 @@
method public androidx.core.os.HeapProfileRequestBuilder setTrackJavaAllocations(boolean traceJavaAllocations);
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class JavaHeapDumpRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.JavaHeapDumpRequestBuilder> {
+ @RequiresApi(api=35) public final class JavaHeapDumpRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.JavaHeapDumpRequestBuilder> {
ctor public JavaHeapDumpRequestBuilder();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected android.os.Bundle getParams();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected int getProfilingType();
@@ -2397,13 +2397,13 @@
}
public final class Profiling {
- method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static kotlinx.coroutines.flow.Flow<android.os.ProfilingResult> registerForAllProfilingResults(android.content.Context context);
- method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void registerForAllProfilingResults(android.content.Context context, java.util.concurrent.Executor executor, java.util.function.Consumer<android.os.ProfilingResult> listener);
- method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void requestProfiling(android.content.Context context, androidx.core.os.ProfilingRequest profilingRequest, java.util.concurrent.Executor? executor, java.util.function.Consumer<android.os.ProfilingResult>? listener);
- method @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static void unregisterForAllProfilingResults(android.content.Context context, java.util.function.Consumer<android.os.ProfilingResult> listener);
+ method @RequiresApi(api=35) public static kotlinx.coroutines.flow.Flow<android.os.ProfilingResult> registerForAllProfilingResults(android.content.Context context);
+ method @RequiresApi(api=35) public static void registerForAllProfilingResults(android.content.Context context, java.util.concurrent.Executor executor, java.util.function.Consumer<android.os.ProfilingResult> listener);
+ method @RequiresApi(api=35) public static void requestProfiling(android.content.Context context, androidx.core.os.ProfilingRequest profilingRequest, java.util.concurrent.Executor? executor, java.util.function.Consumer<android.os.ProfilingResult>? listener);
+ method @RequiresApi(api=35) public static void unregisterForAllProfilingResults(android.content.Context context, java.util.function.Consumer<android.os.ProfilingResult> listener);
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class ProfilingRequest {
+ @RequiresApi(api=35) public final class ProfilingRequest {
method public android.os.CancellationSignal? getCancellationSignal();
method public android.os.Bundle getParams();
method public int getProfilingType();
@@ -2414,7 +2414,7 @@
property public final String? tag;
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public abstract class ProfilingRequestBuilder<T extends androidx.core.os.ProfilingRequestBuilder<T>> {
+ @RequiresApi(api=35) public abstract class ProfilingRequestBuilder<T extends androidx.core.os.ProfilingRequestBuilder<T>> {
method public final androidx.core.os.ProfilingRequest build();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected abstract android.os.Bundle getParams();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected abstract int getProfilingType();
@@ -2423,7 +2423,7 @@
method public final T setTag(String tag);
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class StackSamplingRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.StackSamplingRequestBuilder> {
+ @RequiresApi(api=35) public final class StackSamplingRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.StackSamplingRequestBuilder> {
ctor public StackSamplingRequestBuilder();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected android.os.Bundle getParams();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected int getProfilingType();
@@ -2433,7 +2433,7 @@
method public androidx.core.os.StackSamplingRequestBuilder setSamplingFrequencyHz(int samplingFrequencyHz);
}
- @RequiresApi(api=android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public final class SystemTraceRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.SystemTraceRequestBuilder> {
+ @RequiresApi(api=35) public final class SystemTraceRequestBuilder extends androidx.core.os.ProfilingRequestBuilder<androidx.core.os.SystemTraceRequestBuilder> {
ctor public SystemTraceRequestBuilder();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected android.os.Bundle getParams();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) protected int getProfilingType();
diff --git a/core/core/build.gradle b/core/core/build.gradle
index 92490a9..bae3ce7 100644
--- a/core/core/build.gradle
+++ b/core/core/build.gradle
@@ -106,4 +106,5 @@
description = "Provides backward-compatible implementations of Android platform APIs and " +
"features."
failOnDeprecationWarnings = false
+ samples(project(":core:core:core-samples"))
}
diff --git a/core/core/samples/build.gradle b/core/core/samples/build.gradle
new file mode 100644
index 0000000..0d008cd
--- /dev/null
+++ b/core/core/samples/build.gradle
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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 {
+ compileOnly(project(":annotation:annotation-sampled"))
+ implementation(project(":core:core"))
+}
+android {
+ compileSdk 35
+ namespace "androidx.core.samples"
+}
+androidx {
+ name = "AndroidX Core Samples"
+ type = LibraryType.SAMPLES
+ mavenVersion = LibraryVersions.CORE
+ inceptionYear = "2024"
+ description = "Samples for the AndroidX Core Libraries"
+}
diff --git a/core/core/samples/src/main/java/androidx/core/os/ProfilingSamples.kt b/core/core/samples/src/main/java/androidx/core/os/ProfilingSamples.kt
new file mode 100644
index 0000000..716675b
--- /dev/null
+++ b/core/core/samples/src/main/java/androidx/core/os/ProfilingSamples.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2024 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.os
+
+import android.content.Context
+import android.os.CancellationSignal
+import android.os.ProfilingResult
+import androidx.annotation.Sampled
+import java.util.function.Consumer
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.flow.flowOn
+
+/** Sample showing how to request a java heap dump with various optional parameters. */
+@Sampled
+fun requestJavaHeapDump(context: Context) {
+ val listener =
+ Consumer<ProfilingResult> { profilingResult ->
+ if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
+ doSomethingWithMyFile(profilingResult.resultFilePath)
+ } else {
+ doSomethingWithFailure(profilingResult.errorCode, profilingResult.errorMessage)
+ }
+ }
+
+ requestProfiling(
+ context,
+ JavaHeapDumpRequestBuilder()
+ .setBufferSizeKb(123 /* Requested buffer size in KB */)
+ .setTag("tag" /* Caller supplied tag for identification */)
+ .build(),
+ Dispatchers.IO.asExecutor(), // Your choice of executor for the callback to occur on.
+ listener
+ )
+}
+
+/**
+ * Sample showing how to request a heap profile with various optional parameters and optional
+ * cancellation after the event of interest was captured.
+ */
+@Sampled
+fun requestHeapProfile(context: Context) {
+ val listener =
+ Consumer<ProfilingResult> { profilingResult ->
+ if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
+ doSomethingWithMyFile(profilingResult.resultFilePath)
+ } else {
+ doSomethingWithFailure(profilingResult.errorCode, profilingResult.errorMessage)
+ }
+ }
+
+ val cancellationSignal = CancellationSignal()
+
+ requestProfiling(
+ context,
+ HeapProfileRequestBuilder()
+ .setBufferSizeKb(1000 /* Requested buffer size in KB */)
+ .setDurationMs(5 * 1000 /* Requested profiling duration in milliseconds */)
+ .setTrackJavaAllocations(true)
+ .setSamplingIntervalBytes(100 /* Requested sampling interval in bytes */)
+ .setTag("tag" /* Caller supplied tag for identification */)
+ .setCancellationSignal(cancellationSignal)
+ .build(),
+ Dispatchers.IO.asExecutor(), // Your choice of executor for the callback to occur on.
+ listener
+ )
+
+ // Optionally, wait for something interesting to happen and then stop the profiling to receive
+ // the result as is.
+ cancellationSignal.cancel()
+}
+
+/**
+ * Sample showing how to request a stack sample with various optional parameters and optional
+ * cancellation after the event of interest was captured.
+ */
+@Sampled
+fun requestStackSampling(context: Context) {
+ val listener =
+ Consumer<ProfilingResult> { profilingResult ->
+ if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
+ doSomethingWithMyFile(profilingResult.resultFilePath)
+ } else {
+ doSomethingWithFailure(profilingResult.errorCode, profilingResult.errorMessage)
+ }
+ }
+
+ val cancellationSignal = CancellationSignal()
+
+ requestProfiling(
+ context,
+ StackSamplingRequestBuilder()
+ .setBufferSizeKb(1000 /* Requested buffer size in KB */)
+ .setDurationMs(10 * 1000 /* Requested profiling duration in millisconds */)
+ .setSamplingFrequencyHz(100 /* Requested sampling frequency */)
+ .setTag("tag" /* Caller supplied tag for identification */)
+ .setCancellationSignal(cancellationSignal)
+ .build(),
+ Dispatchers.IO.asExecutor(), // Your choice of executor for the callback to occur on.
+ listener
+ )
+
+ // Optionally, wait for something interesting to happen and then stop the profiling to receive
+ // the result as is.
+ cancellationSignal.cancel()
+}
+
+/**
+ * Sample showing how to request a system trace with various optional parameters and optional
+ * cancellation after the event of interest was captured.
+ */
+@Sampled
+fun requestSystemTrace(context: Context) {
+ val listener =
+ Consumer<ProfilingResult> { profilingResult ->
+ if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
+ doSomethingWithMyFile(profilingResult.resultFilePath)
+ } else {
+ doSomethingWithFailure(profilingResult.errorCode, profilingResult.errorMessage)
+ }
+ }
+
+ val cancellationSignal = CancellationSignal()
+
+ requestProfiling(
+ context,
+ SystemTraceRequestBuilder()
+ .setBufferSizeKb(1000 /* Requested buffer size in KB */)
+ .setDurationMs(60 * 1000 /* Requested profiling duration in millisconds */)
+ .setBufferFillPolicy(BufferFillPolicy.RING_BUFFER /* Buffer fill policy */)
+ .setTag("tag" /* Caller supplied tag for identification */)
+ .setCancellationSignal(cancellationSignal)
+ .build(),
+ Dispatchers.IO.asExecutor(), // Your choice of executor for the callback to occur on.
+ listener
+ )
+
+ // Optionally, wait for something interesting to happen and then stop the profiling to receive
+ // the result as is.
+ cancellationSignal.cancel()
+}
+
+/** Sample showing how to register a listener for all profiling results from your app. */
+@Sampled
+fun registerForAllProfilingResultsSample(context: Context) {
+ val listener =
+ Consumer<ProfilingResult> { profilingResult ->
+ if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
+ doSomethingWithMyFile(profilingResult.resultFilePath)
+ } else {
+ doSomethingWithFailure(profilingResult.errorCode, profilingResult.errorMessage)
+ }
+ }
+
+ registerForAllProfilingResults(
+ context,
+ Dispatchers.IO.asExecutor(), // Your choice of executor for the callback to occur on.
+ listener
+ )
+}
+
+/** Sample showing how to register a flow for all profiling results from your app. */
+@Sampled
+suspend fun registerForAllProfilingResultsFlowSample(context: Context) {
+ val flow = registerForAllProfilingResults(context)
+
+ flow
+ .flowOn(Dispatchers.IO) // Consume files on a background thread
+ .collect { profilingResult ->
+ if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
+ doSomethingWithMyFile(profilingResult.resultFilePath)
+ } else {
+ doSomethingWithFailure(profilingResult.errorCode, profilingResult.errorMessage)
+ }
+ }
+}
+
+@Suppress("UNUSED_PARAMETER") fun doSomethingWithMyFile(filePath: String?) {}
+
+@Suppress("UNUSED_PARAMETER") fun doSomethingWithFailure(errorCode: Int, errorMessage: String?) {}
diff --git a/core/core/src/main/java/androidx/core/os/Profiling.kt b/core/core/src/main/java/androidx/core/os/Profiling.kt
index 55a4c00..683e0fbb 100644
--- a/core/core/src/main/java/androidx/core/os/Profiling.kt
+++ b/core/core/src/main/java/androidx/core/os/Profiling.kt
@@ -19,7 +19,6 @@
package androidx.core.os
import android.content.Context
-import android.os.Build
import android.os.Bundle
import android.os.CancellationSignal
import android.os.ProfilingManager
@@ -32,9 +31,9 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
-/** Helpers providing simple wrapper APIs for {@link ProfilingManager}. */
+/** Helpers providing simple wrapper APIs for [ProfilingManager]. */
-// Begin section: Keep in sync with {@link ProfilingManager}
+// Begin section: Keep in sync with ProfilingManager
private const val KEY_DURATION_MS: String = "KEY_DURATION_MS"
private const val KEY_SAMPLING_INTERVAL_BYTES: String = "KEY_SAMPLING_INTERVAL_BYTES"
private const val KEY_TRACK_JAVA_ALLOCATIONS: String = "KEY_TRACK_JAVA_ALLOCATIONS"
@@ -47,7 +46,10 @@
// End section: Keep in sync with ProfilingManager
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+// TODO: b/361641325 - Change all RequiresApi values from 35 to
+// Build.VERSION_CODES.VANILLA_ICE_CREAM after docs rendering bug is fixed.
+
+@RequiresApi(api = 35)
public sealed class BufferFillPolicy(internal val value: Int) {
public companion object {
@JvmField @SuppressWarnings("AcronymName") public val DISCARD: BufferFillPolicy = Discard()
@@ -62,8 +64,12 @@
private class RingBuffer : BufferFillPolicy(VALUE_BUFFER_FILL_POLICY_RING_BUFFER)
}
-/** Obtain a flow to be called with all profiling results for this UID. */
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+/**
+ * Obtain a flow to be called with all profiling results for this UID.
+ *
+ * @sample androidx.core.os.registerForAllProfilingResultsFlowSample
+ */
+@RequiresApi(api = 35)
public fun registerForAllProfilingResults(context: Context): Flow<ProfilingResult> = callbackFlow {
val listener = Consumer<ProfilingResult> { result -> trySend(result) }
@@ -73,8 +79,12 @@
awaitClose { service.unregisterForAllProfilingResults(listener) }
}
-/** Register a listener to be called with all profiling results for this UID. */
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+/**
+ * Register a listener to be called with all profiling results for this UID.
+ *
+ * @sample androidx.core.os.registerForAllProfilingResultsSample
+ */
+@RequiresApi(api = 35)
public fun registerForAllProfilingResults(
context: Context,
executor: Executor,
@@ -85,19 +95,32 @@
}
/** Unregister a listener that was to be called for all profiling results. */
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@RequiresApi(api = 35)
public fun unregisterForAllProfilingResults(context: Context, listener: Consumer<ProfilingResult>) {
val service = context.getSystemService(ProfilingManager::class.java)
service.unregisterForAllProfilingResults(listener)
}
/**
- * Request profiling using a {@link ProfilingRequest} generated by one of the provided builders.
+ * Request profiling using a [ProfilingRequest] generated by one of the provided builders.
*
* If the executor and/or listener are null, and if no global listener and executor combinations are
- * registered using {@link registerForAllProfilingResults}, the request will be dropped.
+ * registered using [registerForAllProfilingResults], the request will be dropped.
+ *
+ * Requests will be rate limited and are not guaranteed to be filled.
+ *
+ * There might be a delay before profiling begins. For continuous profiling types (system tracing,
+ * stack sampling, and heap profiling), we recommend starting the collection early and stopping it
+ * with cancellationSignal, set on the [profilingRequest] builder, immediately after the area of
+ * interest to ensure that the section you want profiled is captured. For heap dumps, we recommend
+ * testing locally to ensure that the heap dump is collected at the proper time.
+ *
+ * @sample androidx.core.os.requestJavaHeapDump
+ * @sample androidx.core.os.requestHeapProfile
+ * @sample androidx.core.os.requestStackSampling
+ * @sample androidx.core.os.requestSystemTrace
*/
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@RequiresApi(api = 35)
public fun requestProfiling(
context: Context,
profilingRequest: ProfilingRequest,
@@ -116,7 +139,7 @@
}
/** Base class for request builders. */
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@RequiresApi(api = 35)
@SuppressWarnings("StaticFinalBuilder", "TopLevelBuilder")
public abstract class ProfilingRequestBuilder<T : ProfilingRequestBuilder<T>>
internal constructor() {
@@ -142,8 +165,8 @@
}
/**
- * Build the {@link ProfilingRequest} object which can be used with {@link requestProfiling} to
- * request profiling.
+ * Build the [ProfilingRequest] object which can be used with [requestProfiling] to request
+ * profiling.
*/
public fun build(): ProfilingRequest {
return ProfilingRequest(getProfilingType(), getParams(), mTag, mCancellationSignal)
@@ -162,8 +185,12 @@
protected abstract fun getParams(): Bundle
}
-/** Request builder to create a request for a java heap dump from {@link ProfilingManager}. */
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+/**
+ * Request builder to create a request for a java heap dump from [ProfilingManager].
+ *
+ * @sample androidx.core.os.requestJavaHeapDump
+ */
+@RequiresApi(api = 35)
public class JavaHeapDumpRequestBuilder : ProfilingRequestBuilder<JavaHeapDumpRequestBuilder>() {
private val mParams: Bundle = Bundle()
@@ -189,8 +216,12 @@
}
}
-/** Request builder to create a request for a heap profile from {@link ProfilingManager}. */
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+/**
+ * Request builder to create a request for a heap profile from [ProfilingManager].
+ *
+ * @sample androidx.core.os.requestHeapProfile
+ */
+@RequiresApi(api = 35)
public class HeapProfileRequestBuilder : ProfilingRequestBuilder<HeapProfileRequestBuilder>() {
private val mParams: Bundle = Bundle()
@@ -234,8 +265,12 @@
}
}
-/** Request builder to create a request for stack sampling from {@link ProfilingManager}. */
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+/**
+ * Request builder to create a request for stack sampling from [ProfilingManager].
+ *
+ * @sample androidx.core.os.requestStackSampling
+ */
+@RequiresApi(api = 35)
public class StackSamplingRequestBuilder : ProfilingRequestBuilder<StackSamplingRequestBuilder>() {
private val mParams: Bundle = Bundle()
@@ -273,8 +308,12 @@
}
}
-/** Request builder to create a request for a system trace from {@link ProfilingManager}. */
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+/**
+ * Request builder to create a request for a system trace from [ProfilingManager].
+ *
+ * @sample androidx.core.os.requestSystemTrace
+ */
+@RequiresApi(api = 35)
public class SystemTraceRequestBuilder : ProfilingRequestBuilder<SystemTraceRequestBuilder>() {
private val mParams: Bundle = Bundle()
@@ -317,9 +356,9 @@
*
* This should be constructed using one of the provided builders.
*
- * This should be used with {@link requestProfiling} to submit a profiling request.
+ * This should be used with [requestProfiling] to submit a profiling request.
*/
-@RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@RequiresApi(api = 35)
public class ProfilingRequest
internal constructor(
public val profilingType: Int,
diff --git a/credentials/credentials-play-services-auth/build.gradle b/credentials/credentials-play-services-auth/build.gradle
index abd64a6..63d01b8 100644
--- a/credentials/credentials-play-services-auth/build.gradle
+++ b/credentials/credentials-play-services-auth/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -53,6 +55,11 @@
exclude group: "androidx.core"
}
+ implementation(libs.playServicesIdentityCredentials){
+ exclude group: "androidx.loader"
+ exclude group: "androidx.core"
+ }
+
androidTestImplementation(libs.junit)
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testCore)
@@ -82,4 +89,5 @@
inceptionYear = "2022"
description = "sign into apps using play-services-auth library"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/getdigitalcredential/CredentialProviderGetDigitalCredentialControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/getdigitalcredential/CredentialProviderGetDigitalCredentialControllerTest.kt
new file mode 100644
index 0000000..55c7694
--- /dev/null
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/getdigitalcredential/CredentialProviderGetDigitalCredentialControllerTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2024 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.credentials.playservices.getdigitalcredential
+
+import android.content.ComponentName
+import androidx.credentials.ExperimentalDigitalCredentialApi
+import androidx.credentials.GetCredentialRequest
+import androidx.credentials.GetDigitalCredentialOption
+import androidx.credentials.playservices.TestCredentialsActivity
+import androidx.credentials.playservices.TestUtils
+import androidx.credentials.playservices.controllers.GetRestoreCredential.CredentialProviderGetDigitalCredentialController
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@OptIn(ExperimentalDigitalCredentialApi::class)
+class CredentialProviderGetDigitalCredentialControllerTest {
+ @Test
+ fun convertRequestToPlayServices_success() {
+ val request =
+ GetCredentialRequest(
+ credentialOptions =
+ listOf(
+ GetDigitalCredentialOption("{\"request\":{\"json\":{\"test\":\"val\"}}}"),
+ GetDigitalCredentialOption("{\"request\":\"val\",\"key2\":\"val2\"}"),
+ ),
+ origin = "origin",
+ preferIdentityDocUi = true,
+ preferUiBrandingComponentName = ComponentName("pkg", "cls"),
+ preferImmediatelyAvailableCredentials = true,
+ )
+ val activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java)
+
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
+ val convertedRequest =
+ CredentialProviderGetDigitalCredentialController(activity!!)
+ .convertRequestToPlayServices(request)
+
+ assertThat(convertedRequest.origin).isEqualTo(request.origin)
+ TestUtils.equals(
+ convertedRequest.data,
+ GetCredentialRequest.getRequestMetadataBundle(request)
+ )
+ request.credentialOptions.forEachIndexed { idx, expectedOption ->
+ val actualOption = convertedRequest.credentialOptions[idx]
+ assertThat(actualOption.type).isEqualTo(expectedOption.type)
+ if (expectedOption is GetDigitalCredentialOption) {
+ assertThat(actualOption.requestMatcher).isEqualTo(expectedOption.requestJson)
+ }
+ TestUtils.equals(actualOption.credentialRetrievalData, expectedOption.requestData)
+ TestUtils.equals(actualOption.candidateQueryData, expectedOption.candidateQueryData)
+ }
+ }
+ }
+}
diff --git a/credentials/credentials-play-services-auth/src/main/AndroidManifest.xml b/credentials/credentials-play-services-auth/src/main/AndroidManifest.xml
index 24737ae..5a9ac0b 100644
--- a/credentials/credentials-play-services-auth/src/main/AndroidManifest.xml
+++ b/credentials/credentials-play-services-auth/src/main/AndroidManifest.xml
@@ -33,5 +33,13 @@
android:fitsSystemWindows="true"
android:theme="@style/Theme.Hidden">
</activity>
+ <activity
+ android:name="androidx.credentials.playservices.IdentityCredentialApiHiddenActivity"
+ android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
+ android:exported="false"
+ android:enabled="true"
+ android:fitsSystemWindows="true"
+ android:theme="@style/Theme.Hidden">
+ </activity>
</application>
</manifest>
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
index 8bed458..e702dda 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
@@ -30,8 +30,10 @@
import androidx.credentials.CreateRestoreCredentialRequest
import androidx.credentials.CredentialManagerCallback
import androidx.credentials.CredentialProvider
+import androidx.credentials.ExperimentalDigitalCredentialApi
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetCredentialResponse
+import androidx.credentials.GetDigitalCredentialOption
import androidx.credentials.GetRestoreCredentialOption
import androidx.credentials.exceptions.ClearCredentialException
import androidx.credentials.exceptions.ClearCredentialProviderConfigurationException
@@ -44,6 +46,7 @@
import androidx.credentials.playservices.controllers.CreatePassword.CredentialProviderCreatePasswordController
import androidx.credentials.playservices.controllers.CreatePublicKeyCredential.CredentialProviderCreatePublicKeyCredentialController
import androidx.credentials.playservices.controllers.CreateRestoreCredential.CredentialProviderCreateRestoreCredentialController
+import androidx.credentials.playservices.controllers.GetRestoreCredential.CredentialProviderGetDigitalCredentialController
import androidx.credentials.playservices.controllers.GetRestoreCredential.CredentialProviderGetRestoreCredentialController
import androidx.credentials.playservices.controllers.GetSignInIntent.CredentialProviderGetSignInIntentController
import com.google.android.gms.auth.api.identity.Identity
@@ -58,6 +61,7 @@
/** Entry point of all credential manager requests to the play-services-auth module. */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Suppress("deprecation")
+@OptIn(ExperimentalDigitalCredentialApi::class)
class CredentialProviderPlayServicesImpl(private val context: Context) : CredentialProvider {
@VisibleForTesting var googleApiAvailability = GoogleApiAvailability.getInstance()
@@ -72,7 +76,23 @@
if (cancellationReviewer(cancellationSignal)) {
return
}
- if (isGetRestoreCredentialRequest(request)) {
+ if (isDigitalCredentialRequest(request)) {
+ if (!isAvailableOnDevice(MIN_GMS_APK_VERSION_DIGITAL_CRED)) {
+ cancellationReviewerWithCallback(cancellationSignal) {
+ executor.execute {
+ callback.onError(
+ GetCredentialProviderConfigurationException(
+ "this device requires a Google Play Services update for the" +
+ " given feature to be supported"
+ )
+ )
+ }
+ }
+ return
+ }
+ CredentialProviderGetDigitalCredentialController(context)
+ .invokePlayServices(request, callback, executor, cancellationSignal)
+ } else if (isGetRestoreCredentialRequest(request)) {
if (!isAvailableOnDevice(MIN_GMS_APK_VERSION_RESTORE_CRED)) {
cancellationReviewerWithCallback(cancellationSignal) {
executor.execute {
@@ -263,6 +283,8 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) const val MIN_GMS_APK_VERSION = 230815045
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
const val MIN_GMS_APK_VERSION_RESTORE_CRED = 242200000
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ const val MIN_GMS_APK_VERSION_DIGITAL_CRED = 243100000
internal fun cancellationReviewerWithCallback(
cancellationSignal: CancellationSignal?,
@@ -302,5 +324,14 @@
}
return false
}
+
+ internal fun isDigitalCredentialRequest(request: GetCredentialRequest): Boolean {
+ for (option in request.credentialOptions) {
+ if (option is GetDigitalCredentialOption) {
+ return true
+ }
+ }
+ return false
+ }
}
}
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt
index 03d22ff..93216a1 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt
@@ -32,6 +32,8 @@
import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_INTERRUPTED
import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_NO_CREDENTIALS
import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_UNKNOWN
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.reportError
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.reportResult
import com.google.android.gms.auth.api.identity.BeginSignInRequest
import com.google.android.gms.auth.api.identity.GetSignInIntentRequest
import com.google.android.gms.auth.api.identity.Identity
@@ -150,11 +152,7 @@
}
private fun setupFailure(resultReceiver: ResultReceiver, errName: String, errMsg: String) {
- val bundle = Bundle()
- bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, true)
- bundle.putString(CredentialProviderBaseController.EXCEPTION_TYPE_TAG, errName)
- bundle.putString(CredentialProviderBaseController.EXCEPTION_MESSAGE_TAG, errMsg)
- resultReceiver.send(Integer.MAX_VALUE, bundle)
+ resultReceiver.reportError(errName, errMsg)
finish()
}
@@ -336,11 +334,11 @@
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
- val bundle = Bundle()
- bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, false)
- bundle.putInt(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, requestCode)
- bundle.putParcelable(CredentialProviderBaseController.RESULT_DATA_TAG, data)
- resultReceiver?.send(resultCode, bundle)
+ resultReceiver?.reportResult(
+ requestCode = requestCode,
+ data = data,
+ resultCode = resultCode
+ )
mWaitingForActivityResult = false
finish()
}
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/IdentityCredentialApiHiddenActivity.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/IdentityCredentialApiHiddenActivity.kt
new file mode 100644
index 0000000..7270a9a
--- /dev/null
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/IdentityCredentialApiHiddenActivity.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("Deprecation")
+
+package androidx.credentials.playservices
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.ResultReceiver
+import androidx.annotation.RestrictTo
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_UNKNOWN
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.reportError
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.reportResult
+
+/** An activity used to ensure all required API versions work as intended. */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@Suppress("ForbiddenSuperClass")
+open class IdentityCredentialApiHiddenActivity : Activity() {
+
+ private var resultReceiver: ResultReceiver? = null
+ private var mWaitingForActivityResult = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ overridePendingTransition(0, 0)
+ resultReceiver =
+ intent.getParcelableExtra(CredentialProviderBaseController.RESULT_RECEIVER_TAG)
+ if (resultReceiver == null) {
+ finish()
+ }
+
+ restoreState(savedInstanceState)
+ if (mWaitingForActivityResult) {
+ return
+ // Past call still active
+ }
+ val pendingIntent: PendingIntent? =
+ intent.getParcelableExtra(CredentialProviderBaseController.EXTRA_GET_CREDENTIAL_INTENT)
+
+ if (pendingIntent != null) {
+ startIntentSenderForResult(
+ pendingIntent.intentSender,
+ /* requestCode= */ CredentialProviderBaseController.CONTROLLER_REQUEST_CODE,
+ /* fillInIntent= */ null,
+ /* flagsMask= */ 0,
+ /* flagsValues= */ 0,
+ /* extraFlags= */ 0,
+ /* options = */ null
+ )
+ } else {
+ resultReceiver?.reportError(errName = GET_UNKNOWN, errMsg = "Internal error")
+ finish()
+ }
+ }
+
+ private fun restoreState(savedInstanceState: Bundle?) {
+ if (savedInstanceState != null) {
+ mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false)
+ }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult)
+ super.onSaveInstanceState(outState)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ resultReceiver?.reportResult(
+ requestCode = requestCode,
+ resultCode = resultCode,
+ data = data
+ )
+ mWaitingForActivityResult = false
+ finish()
+ }
+
+ companion object {
+ private const val KEY_AWAITING_RESULT = "androidx.credentials.playservices.AWAITING_RESULT"
+ }
+}
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderBaseController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderBaseController.kt
index 584fc1be..b5719fb 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderBaseController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderBaseController.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.Intent
+import android.os.Bundle
import android.os.Parcel
import android.os.ResultReceiver
import androidx.credentials.exceptions.CreateCredentialCancellationException
@@ -44,7 +45,7 @@
)
// Generic controller request code used by all controllers
- @JvmStatic protected val CONTROLLER_REQUEST_CODE: Int = 1
+ @JvmStatic internal val CONTROLLER_REQUEST_CODE: Int = 1
/** -- Used to avoid reflection, these constants map errors from HiddenActivity -- */
const val GET_CANCELED = "GET_CANCELED_TAG"
@@ -79,6 +80,9 @@
// Key for the result intent to send back to the controller
const val RESULT_DATA_TAG = "RESULT_DATA"
+ // Key for the actual parcelable type sent to the hidden activity
+ const val EXTRA_GET_CREDENTIAL_INTENT = "EXTRA_GET_CREDENTIAL_INTENT"
+
// Key for the failure boolean sent back from hidden activity to controller
const val FAILURE_RESPONSE_TAG = "FAILURE_RESPONSE"
@@ -115,6 +119,22 @@
}
}
+ internal fun ResultReceiver.reportError(errName: String, errMsg: String) {
+ val bundle = Bundle()
+ bundle.putBoolean(FAILURE_RESPONSE_TAG, true)
+ bundle.putString(EXCEPTION_TYPE_TAG, errName)
+ bundle.putString(EXCEPTION_MESSAGE_TAG, errMsg)
+ this.send(Integer.MAX_VALUE, bundle)
+ }
+
+ internal fun ResultReceiver.reportResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ val bundle = Bundle()
+ bundle.putBoolean(FAILURE_RESPONSE_TAG, false)
+ bundle.putInt(ACTIVITY_REQUEST_CODE_TAG, requestCode)
+ bundle.putParcelable(RESULT_DATA_TAG, data)
+ this.send(resultCode, bundle)
+ }
+
internal fun createCredentialExceptionTypeToException(
typeName: String?,
msg: String?
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/GetDigitalCredential/CredentialProviderGetDigitalCredentialController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/GetDigitalCredential/CredentialProviderGetDigitalCredentialController.kt
new file mode 100644
index 0000000..b606ece
--- /dev/null
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/GetDigitalCredential/CredentialProviderGetDigitalCredentialController.kt
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2024 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.credentials.playservices.controllers.GetRestoreCredential
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.CancellationSignal
+import android.os.Handler
+import android.os.Looper
+import android.os.ResultReceiver
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import androidx.credentials.Credential
+import androidx.credentials.CredentialManagerCallback
+import androidx.credentials.DigitalCredential
+import androidx.credentials.ExperimentalDigitalCredentialApi
+import androidx.credentials.GetCredentialRequest
+import androidx.credentials.GetCredentialResponse
+import androidx.credentials.GetDigitalCredentialOption
+import androidx.credentials.exceptions.GetCredentialCancellationException
+import androidx.credentials.exceptions.GetCredentialException
+import androidx.credentials.exceptions.GetCredentialInterruptedException
+import androidx.credentials.exceptions.GetCredentialUnknownException
+import androidx.credentials.internal.toJetpackGetException
+import androidx.credentials.playservices.CredentialProviderPlayServicesImpl
+import androidx.credentials.playservices.IdentityCredentialApiHiddenActivity
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController
+import androidx.credentials.playservices.controllers.CredentialProviderController
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.common.api.CommonStatusCodes
+import com.google.android.gms.identitycredentials.IdentityCredentialManager
+import com.google.android.gms.identitycredentials.IntentHelper
+import java.util.concurrent.Executor
+
+/** A controller to handle the GetRestoreCredential flow with play services. */
+@OptIn(ExperimentalDigitalCredentialApi::class)
+internal class CredentialProviderGetDigitalCredentialController(private val context: Context) :
+ CredentialProviderController<
+ GetCredentialRequest,
+ com.google.android.gms.identitycredentials.GetCredentialRequest,
+ com.google.android.gms.identitycredentials.GetCredentialResponse,
+ GetCredentialResponse,
+ GetCredentialException
+ >(context) {
+
+ /** The callback object state, used in the protected handleResponse method. */
+ @VisibleForTesting
+ lateinit var callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>
+
+ /** The callback requires an executor to invoke it. */
+ @VisibleForTesting lateinit var executor: Executor
+
+ /**
+ * The cancellation signal, which is shuttled around to stop the flow at any moment prior to
+ * returning data.
+ */
+ @VisibleForTesting private var cancellationSignal: CancellationSignal? = null
+
+ @Suppress("deprecation")
+ private val resultReceiver =
+ object : ResultReceiver(Handler(Looper.getMainLooper())) {
+ public override fun onReceiveResult(resultCode: Int, resultData: Bundle) {
+ if (
+ maybeReportErrorFromResultReceiver(
+ resultData,
+ CredentialProviderBaseController.Companion::
+ getCredentialExceptionTypeToException,
+ executor,
+ callback,
+ cancellationSignal
+ )
+ ) {
+ return
+ } else {
+ handleResponse(
+ resultData.getInt(ACTIVITY_REQUEST_CODE_TAG),
+ resultCode,
+ resultData.getParcelable(RESULT_DATA_TAG)
+ )
+ }
+ }
+ }
+
+ internal fun handleResponse(uniqueRequestCode: Int, resultCode: Int, data: Intent?) {
+ if (uniqueRequestCode != CONTROLLER_REQUEST_CODE) {
+ Log.w(
+ TAG,
+ "Returned request code $CONTROLLER_REQUEST_CODE which " +
+ " does not match what was given $uniqueRequestCode"
+ )
+ return
+ }
+
+ if (
+ maybeReportErrorResultCodeGet(
+ resultCode,
+ { s, f -> cancelOrCallbackExceptionOrResult(s, f) },
+ { e -> this.executor.execute { this.callback.onError(e) } },
+ cancellationSignal
+ )
+ ) {
+ return
+ }
+
+ try {
+ val response = IntentHelper.extractGetCredentialResponse(resultCode, data?.extras!!)
+ cancelOrCallbackExceptionOrResult(cancellationSignal) {
+ this.executor.execute {
+ this.callback.onResult(convertResponseToCredentialManager(response))
+ }
+ }
+ } catch (e: Exception) {
+ val getException = fromGmsException(e)
+ cancelOrCallbackExceptionOrResult(cancellationSignal) {
+ executor.execute { callback.onError(getException) }
+ }
+ }
+ }
+
+ override fun invokePlayServices(
+ request: GetCredentialRequest,
+ callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>,
+ executor: Executor,
+ cancellationSignal: CancellationSignal?
+ ) {
+ this.cancellationSignal = cancellationSignal
+ this.callback = callback
+ this.executor = executor
+
+ if (CredentialProviderPlayServicesImpl.cancellationReviewer(cancellationSignal)) {
+ return
+ }
+
+ val convertedRequest = this.convertRequestToPlayServices(request)
+ IdentityCredentialManager.getClient(context)
+ .getCredential(convertedRequest)
+ .addOnSuccessListener { result ->
+ if (CredentialProviderPlayServicesImpl.cancellationReviewer(cancellationSignal)) {
+ return@addOnSuccessListener
+ }
+ val hiddenIntent = Intent(context, IdentityCredentialApiHiddenActivity::class.java)
+ hiddenIntent.flags = Intent.FLAG_ACTIVITY_NO_ANIMATION
+ hiddenIntent.putExtra(
+ RESULT_RECEIVER_TAG,
+ toIpcFriendlyResultReceiver(resultReceiver)
+ )
+ hiddenIntent.putExtra(EXTRA_GET_CREDENTIAL_INTENT, result.pendingIntent)
+ context.startActivity(hiddenIntent)
+ }
+ .addOnFailureListener { e ->
+ val getException = fromGmsException(e)
+ cancelOrCallbackExceptionOrResult(cancellationSignal) {
+ executor.execute { callback.onError(getException) }
+ }
+ }
+ }
+
+ private fun fromGmsException(e: Throwable): GetCredentialException {
+ return when (e) {
+ is com.google.android.gms.identitycredentials.GetCredentialException ->
+ toJetpackGetException(e.type, e.message)
+ is ApiException ->
+ when (e.statusCode) {
+ CommonStatusCodes.CANCELED -> {
+ GetCredentialCancellationException(e.message)
+ }
+ in retryables -> {
+ GetCredentialInterruptedException(e.message)
+ }
+ else -> {
+ GetCredentialUnknownException("Get digital credential failed, failure: $e")
+ }
+ }
+ else -> GetCredentialUnknownException("Get digital credential failed, failure: $e")
+ }
+ }
+
+ public override fun convertRequestToPlayServices(
+ request: GetCredentialRequest
+ ): com.google.android.gms.identitycredentials.GetCredentialRequest {
+ val credOptions =
+ mutableListOf<com.google.android.gms.identitycredentials.CredentialOption>()
+ for (option in request.credentialOptions) {
+ if (option is GetDigitalCredentialOption) {
+ credOptions.add(
+ com.google.android.gms.identitycredentials.CredentialOption(
+ option.type,
+ option.requestData,
+ option.candidateQueryData,
+ option.requestJson,
+ requestType = "",
+ protocolType = "",
+ )
+ )
+ }
+ }
+ return com.google.android.gms.identitycredentials.GetCredentialRequest(
+ credOptions,
+ GetCredentialRequest.getRequestMetadataBundle(request),
+ request.origin,
+ ResultReceiver(null) // No-op
+ )
+ }
+
+ public override fun convertResponseToCredentialManager(
+ response: com.google.android.gms.identitycredentials.GetCredentialResponse
+ ): GetCredentialResponse {
+ return GetCredentialResponse(
+ Credential.createFrom(
+ DigitalCredential.TYPE_DIGITAL_CREDENTIAL, // TODO: b/361100869 - use the real type
+ // returned as the response
+ response.credential.data,
+ )
+ )
+ }
+
+ private companion object {
+ private const val TAG = "DigitalCredentialClient"
+ }
+}
diff --git a/credentials/credentials/api/current.txt b/credentials/credentials/api/current.txt
index 50e153f..db192ff 100644
--- a/credentials/credentials/api/current.txt
+++ b/credentials/credentials/api/current.txt
@@ -122,8 +122,7 @@
}
public final class CreateRestoreCredentialResponse extends androidx.credentials.CreateCredentialResponse {
- ctor public CreateRestoreCredentialResponse(String responseJson, android.os.Bundle data);
- method public static androidx.credentials.CreateRestoreCredentialResponse createFrom(android.os.Bundle data);
+ ctor public CreateRestoreCredentialResponse(String responseJson);
method public String getResponseJson();
property public final String responseJson;
field public static final String BUNDLE_KEY_CREATE_RESTORE_CREDENTIAL_RESPONSE = "androidx.credentials.BUNDLE_KEY_CREATE_RESTORE_CREDENTIAL_RESPONSE";
@@ -131,7 +130,6 @@
}
public static final class CreateRestoreCredentialResponse.Companion {
- method public androidx.credentials.CreateRestoreCredentialResponse createFrom(android.os.Bundle data);
}
public abstract class Credential {
@@ -221,6 +219,20 @@
ctor public CustomCredential(String type, android.os.Bundle data);
}
+ @SuppressCompatibility @androidx.credentials.ExperimentalDigitalCredentialApi public final class DigitalCredential extends androidx.credentials.Credential {
+ ctor public DigitalCredential(String credentialJson);
+ method public String getCredentialJson();
+ property public final String credentialJson;
+ field public static final androidx.credentials.DigitalCredential.Companion Companion;
+ field public static final String TYPE_DIGITAL_CREDENTIAL = "androidx.credentials.TYPE_DIGITAL_CREDENTIAL";
+ }
+
+ public static final class DigitalCredential.Companion {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="This CredentialManager API is experimental and is likely to change or to be removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalDigitalCredentialApi {
+ }
+
public final class GetCredentialRequest {
ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions);
ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, optional String? origin);
@@ -273,6 +285,12 @@
ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired, optional boolean isAutoSelectAllowed, optional java.util.Set<android.content.ComponentName> allowedProviders, optional int typePriorityHint);
}
+ @SuppressCompatibility @androidx.credentials.ExperimentalDigitalCredentialApi public final class GetDigitalCredentialOption extends androidx.credentials.CredentialOption {
+ ctor public GetDigitalCredentialOption(String requestJson);
+ method public String getRequestJson();
+ property public final String requestJson;
+ }
+
public final class GetPasswordOption extends androidx.credentials.CredentialOption {
ctor public GetPasswordOption();
ctor public GetPasswordOption(optional java.util.Set<java.lang.String> allowedUserIds);
@@ -870,7 +888,6 @@
@RequiresApi(35) public final class BiometricPromptData {
ctor public BiometricPromptData();
- ctor public BiometricPromptData();
ctor public BiometricPromptData(optional androidx.biometric.BiometricPrompt.CryptoObject? cryptoObject);
ctor public BiometricPromptData(optional androidx.biometric.BiometricPrompt.CryptoObject? cryptoObject, optional int allowedAuthenticators);
method public int getAllowedAuthenticators();
diff --git a/credentials/credentials/api/restricted_current.txt b/credentials/credentials/api/restricted_current.txt
index 50e153f..db192ff 100644
--- a/credentials/credentials/api/restricted_current.txt
+++ b/credentials/credentials/api/restricted_current.txt
@@ -122,8 +122,7 @@
}
public final class CreateRestoreCredentialResponse extends androidx.credentials.CreateCredentialResponse {
- ctor public CreateRestoreCredentialResponse(String responseJson, android.os.Bundle data);
- method public static androidx.credentials.CreateRestoreCredentialResponse createFrom(android.os.Bundle data);
+ ctor public CreateRestoreCredentialResponse(String responseJson);
method public String getResponseJson();
property public final String responseJson;
field public static final String BUNDLE_KEY_CREATE_RESTORE_CREDENTIAL_RESPONSE = "androidx.credentials.BUNDLE_KEY_CREATE_RESTORE_CREDENTIAL_RESPONSE";
@@ -131,7 +130,6 @@
}
public static final class CreateRestoreCredentialResponse.Companion {
- method public androidx.credentials.CreateRestoreCredentialResponse createFrom(android.os.Bundle data);
}
public abstract class Credential {
@@ -221,6 +219,20 @@
ctor public CustomCredential(String type, android.os.Bundle data);
}
+ @SuppressCompatibility @androidx.credentials.ExperimentalDigitalCredentialApi public final class DigitalCredential extends androidx.credentials.Credential {
+ ctor public DigitalCredential(String credentialJson);
+ method public String getCredentialJson();
+ property public final String credentialJson;
+ field public static final androidx.credentials.DigitalCredential.Companion Companion;
+ field public static final String TYPE_DIGITAL_CREDENTIAL = "androidx.credentials.TYPE_DIGITAL_CREDENTIAL";
+ }
+
+ public static final class DigitalCredential.Companion {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="This CredentialManager API is experimental and is likely to change or to be removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalDigitalCredentialApi {
+ }
+
public final class GetCredentialRequest {
ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions);
ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, optional String? origin);
@@ -273,6 +285,12 @@
ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired, optional boolean isAutoSelectAllowed, optional java.util.Set<android.content.ComponentName> allowedProviders, optional int typePriorityHint);
}
+ @SuppressCompatibility @androidx.credentials.ExperimentalDigitalCredentialApi public final class GetDigitalCredentialOption extends androidx.credentials.CredentialOption {
+ ctor public GetDigitalCredentialOption(String requestJson);
+ method public String getRequestJson();
+ property public final String requestJson;
+ }
+
public final class GetPasswordOption extends androidx.credentials.CredentialOption {
ctor public GetPasswordOption();
ctor public GetPasswordOption(optional java.util.Set<java.lang.String> allowedUserIds);
@@ -870,7 +888,6 @@
@RequiresApi(35) public final class BiometricPromptData {
ctor public BiometricPromptData();
- ctor public BiometricPromptData();
ctor public BiometricPromptData(optional androidx.biometric.BiometricPrompt.CryptoObject? cryptoObject);
ctor public BiometricPromptData(optional androidx.biometric.BiometricPrompt.CryptoObject? cryptoObject, optional int allowedAuthenticators);
method public int getAllowedAuthenticators();
diff --git a/credentials/credentials/build.gradle b/credentials/credentials/build.gradle
index eb89d1a..ce89b90 100644
--- a/credentials/credentials/build.gradle
+++ b/credentials/credentials/build.gradle
@@ -31,7 +31,7 @@
dependencies {
api("androidx.annotation:annotation:1.8.1")
- api(project(":biometric:biometric-ktx"))
+ api("androidx.biometric:biometric-ktx:1.4.0-alpha02")
api(libs.kotlinStdlib)
implementation(libs.kotlinCoroutinesCore)
implementation("androidx.core:core:1.15.0-alpha01")
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/DigitalCredentialJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/DigitalCredentialJavaTest.java
new file mode 100644
index 0000000..2168792
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/DigitalCredentialJavaTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2024 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.credentials;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Bundle;
+
+import androidx.annotation.OptIn;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@OptIn(markerClass = ExperimentalDigitalCredentialApi.class)
+public class DigitalCredentialJavaTest {
+ private static final String TEST_CREDENTIAL_JSON =
+ "{\"protocol\":{\"preview\":{\"test\":\"val\"}}}";
+ @Test
+ public void typeConstant() {
+ assertThat(DigitalCredential.TYPE_DIGITAL_CREDENTIAL)
+ .isEqualTo("androidx.credentials.TYPE_DIGITAL_CREDENTIAL");
+ }
+
+ @Test
+ public void constructor_emptyCredentialJson_throws() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new DigitalCredential("")
+ );
+ }
+
+
+ @Test
+ public void constructor_invalidCredentialJsonFormat_throws() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new DigitalCredential("hello")
+ );
+ }
+
+ @Test
+ public void constructorAndGetter() {
+ DigitalCredential credential = new DigitalCredential(TEST_CREDENTIAL_JSON);
+ assertThat(credential.getCredentialJson()).isEqualTo(TEST_CREDENTIAL_JSON);
+ }
+
+ @Test
+ public void frameworkConversion_success() {
+ DigitalCredential credential = new DigitalCredential(TEST_CREDENTIAL_JSON);
+ // Add additional data to the request data and candidate query data to make sure
+ // they persist after the conversion
+ Bundle data = credential.getData();
+ String customDataKey = "customRequestDataKey";
+ CharSequence customDataValue = "customRequestDataValue";
+ data.putCharSequence(customDataKey, customDataValue);
+
+ Credential convertedCredential = Credential.createFrom(
+ credential.getType(), data);
+
+ assertThat(convertedCredential).isInstanceOf(DigitalCredential.class);
+ DigitalCredential convertedSubclassCredential = (DigitalCredential) convertedCredential;
+ assertThat(convertedSubclassCredential.getCredentialJson())
+ .isEqualTo(credential.getCredentialJson());
+ assertThat(convertedCredential.getData().getCharSequence(customDataKey))
+ .isEqualTo(customDataValue);
+ }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/DigitalCredentialTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/DigitalCredentialTest.kt
new file mode 100644
index 0000000..7221999
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/DigitalCredentialTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024 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.credentials
+
+import androidx.credentials.Credential.Companion.createFrom
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.testutils.assertThrows
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@OptIn(ExperimentalDigitalCredentialApi::class)
+class DigitalCredentialTest {
+ @Test
+ fun typeConstant() {
+ assertThat(DigitalCredential.TYPE_DIGITAL_CREDENTIAL)
+ .isEqualTo("androidx.credentials.TYPE_DIGITAL_CREDENTIAL")
+ }
+
+ @Test
+ fun constructor_emptyCredentialJson_throws() {
+ assertThrows(IllegalArgumentException::class.java) { DigitalCredential("") }
+ }
+
+ @Test
+ fun constructor_invalidCredentialJsonFormat_throws() {
+ assertThrows(IllegalArgumentException::class.java) { DigitalCredential("hello") }
+ }
+
+ @Test
+ fun constructorAndGetter() {
+ val credential = DigitalCredential(TEST_CREDENTIAL_JSON)
+ assertThat(credential.credentialJson).isEqualTo(TEST_CREDENTIAL_JSON)
+ }
+
+ @Test
+ fun frameworkConversion_success() {
+ val credential = DigitalCredential(TEST_CREDENTIAL_JSON)
+ // Add additional data to the request data and candidate query data to make sure
+ // they persist after the conversion
+ val data = credential.data
+ val customDataKey = "customRequestDataKey"
+ val customDataValue: CharSequence = "customRequestDataValue"
+ data.putCharSequence(customDataKey, customDataValue)
+
+ val convertedCredential = createFrom(credential.type, data)
+
+ assertThat(convertedCredential).isInstanceOf(DigitalCredential::class.java)
+ val convertedSubclassCredential = convertedCredential as DigitalCredential
+ assertThat(convertedSubclassCredential.credentialJson).isEqualTo(credential.credentialJson)
+ assertThat(convertedCredential.data.getCharSequence(customDataKey))
+ .isEqualTo(customDataValue)
+ }
+
+ companion object {
+ private const val TEST_CREDENTIAL_JSON = "{\"protocol\":{\"preview\":{\"test\":\"val\"}}}"
+ }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetDigitalCredentialOptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetDigitalCredentialOptionJavaTest.java
new file mode 100644
index 0000000..ec4ecd1
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetDigitalCredentialOptionJavaTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.credentials;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+
+import androidx.annotation.OptIn;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@OptIn(markerClass = ExperimentalDigitalCredentialApi.class)
+public class GetDigitalCredentialOptionJavaTest {
+ private static final String TEST_REQUEST_JSON =
+ "{\"protocol\":{\"preview\":{\"test\":\"val\"}}}";
+
+ private static final int EXPECTED_PRIORITY =
+ CredentialOption.PRIORITY_PASSKEY_OR_SIMILAR;
+
+ @Test
+ public void constructorAndGetter() {
+ GetDigitalCredentialOption option = new GetDigitalCredentialOption(TEST_REQUEST_JSON);
+
+ assertThat(option.getRequestJson()).isEqualTo(TEST_REQUEST_JSON);
+ assertThat(option.getAllowedProviders()).isEmpty();
+ assertThat(option.isSystemProviderRequired()).isFalse();
+ assertThat(option.isAutoSelectAllowed()).isFalse();
+ assertThat(option.getType()).isEqualTo(DigitalCredential.TYPE_DIGITAL_CREDENTIAL);
+ assertThat(option.getTypePriorityHint()).isEqualTo(EXPECTED_PRIORITY);
+ }
+
+ @Test
+ public void frameworkConversion_success() {
+ GetDigitalCredentialOption option = new GetDigitalCredentialOption(TEST_REQUEST_JSON);
+ // Add additional data to the request data and candidate query data to make sure
+ // they persist after the conversion
+ Bundle requestData = option.getRequestData();
+ String customRequestDataKey = "customRequestDataKey";
+ String customRequestDataValue = "customRequestDataValue";
+ requestData.putString(customRequestDataKey, customRequestDataValue);
+ Bundle candidateQueryData = option.getCandidateQueryData();
+ String customCandidateQueryDataKey = "customRequestDataKey";
+ boolean customCandidateQueryDataValue = true;
+ candidateQueryData.putBoolean(customCandidateQueryDataKey, customCandidateQueryDataValue);
+
+ CredentialOption convertedOption = CredentialOption.createFrom(
+ option.getType(), requestData, candidateQueryData,
+ option.isSystemProviderRequired(), option.getAllowedProviders());
+
+ assertThat(convertedOption).isInstanceOf(GetDigitalCredentialOption.class);
+ GetDigitalCredentialOption actualOption = (GetDigitalCredentialOption) convertedOption;
+ assertThat(actualOption.isAutoSelectAllowed()).isFalse();
+ assertThat(actualOption.getAllowedProviders()).isEmpty();
+ assertThat(actualOption.getRequestJson()).isEqualTo(TEST_REQUEST_JSON);
+ assertThat(convertedOption.getRequestData().getString(customRequestDataKey))
+ .isEqualTo(customRequestDataValue);
+ assertThat(convertedOption.getCandidateQueryData().getBoolean(customCandidateQueryDataKey))
+ .isEqualTo(customCandidateQueryDataValue);
+ assertThat(convertedOption.getTypePriorityHint()).isEqualTo(EXPECTED_PRIORITY);
+ }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetDigitalCredentialOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetDigitalCredentialOptionTest.kt
new file mode 100644
index 0000000..b0e1240f
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetDigitalCredentialOptionTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.credentials
+
+import androidx.credentials.CredentialOption.Companion.createFrom
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@OptIn(ExperimentalDigitalCredentialApi::class)
+class GetDigitalCredentialOptionTest {
+ @Test
+ fun constructorAndGetter() {
+ val option = GetDigitalCredentialOption(TEST_REQUEST_JSON)
+
+ assertThat(option.requestJson).isEqualTo(TEST_REQUEST_JSON)
+ assertThat(option.allowedProviders).isEmpty()
+ assertThat(option.isSystemProviderRequired).isFalse()
+ assertThat(option.isAutoSelectAllowed).isFalse()
+ assertThat(option.type).isEqualTo(DigitalCredential.TYPE_DIGITAL_CREDENTIAL)
+ assertThat(option.typePriorityHint).isEqualTo(EXPECTED_PRIORITY)
+ }
+
+ @Test
+ fun frameworkConversion_success() {
+ val option = GetDigitalCredentialOption(TEST_REQUEST_JSON)
+ // Add additional data to the request data and candidate query data to make sure
+ // they persist after the conversion
+ val requestData = option.requestData
+ val customRequestDataKey = "customRequestDataKey"
+ val customRequestDataValue = "customRequestDataValue"
+ requestData.putString(customRequestDataKey, customRequestDataValue)
+ val candidateQueryData = option.candidateQueryData
+ val customCandidateQueryDataKey = "customRequestDataKey"
+ val customCandidateQueryDataValue = true
+ candidateQueryData.putBoolean(customCandidateQueryDataKey, customCandidateQueryDataValue)
+
+ val convertedOption =
+ createFrom(
+ option.type,
+ requestData,
+ candidateQueryData,
+ option.isSystemProviderRequired,
+ option.allowedProviders
+ )
+
+ assertThat(convertedOption).isInstanceOf(GetDigitalCredentialOption::class.java)
+ val actualOption = convertedOption as GetDigitalCredentialOption
+ assertThat(actualOption.isAutoSelectAllowed).isFalse()
+ assertThat(actualOption.allowedProviders).isEmpty()
+ assertThat(actualOption.requestJson).isEqualTo(TEST_REQUEST_JSON)
+ assertThat(convertedOption.requestData.getString(customRequestDataKey))
+ .isEqualTo(customRequestDataValue)
+ assertThat(convertedOption.candidateQueryData.getBoolean(customCandidateQueryDataKey))
+ .isEqualTo(customCandidateQueryDataValue)
+ assertThat(convertedOption.typePriorityHint).isEqualTo(EXPECTED_PRIORITY)
+ }
+
+ companion object {
+ private const val TEST_REQUEST_JSON = "{\"protocol\":{\"preview\":{\"test\":\"val\"}}}"
+
+ private const val EXPECTED_PRIORITY = CredentialOption.PRIORITY_PASSKEY_OR_SIMILAR
+ }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java
index 4cb0d808..0a2d095 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BiometricPromptDataJavaTest.java
@@ -41,7 +41,7 @@
public class BiometricPromptDataJavaTest {
private static final BiometricPrompt.CryptoObject TEST_CRYPTO_OBJECT = BiometricTestUtils
- .INSTANCE.createCryptoObject$credentials_debugAndroidTest();
+ .INSTANCE.createCryptoObject$credentials_releaseAndroidTest();
private static final long DEFAULT_BUNDLE_LONG_FOR_CRYPTO_ID = 0L;
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerApi34JavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerApi34JavaTest.java
index f6cf56d..031750f 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerApi34JavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerApi34JavaTest.java
@@ -73,7 +73,7 @@
public void test_retrieveProviderCreateCredReqWithSuccessBpAuthJetpack_retrieveJetpackResult() {
for (int jetpackResult :
AuthenticationResult.Companion
- .getBiometricFrameworkToJetpackResultMap$credentials_debug().values()) {
+ .getBiometricFrameworkToJetpackResultMap$credentials_release().values()) {
BiometricPromptResult biometricPromptResult =
new BiometricPromptResult(new AuthenticationResult(jetpackResult));
android.service.credentials.CreateCredentialRequest request =
@@ -97,7 +97,7 @@
public void test_retrieveProviderGetCredReqWithSuccessBpAuthJetpack_retrieveJetpackResult() {
for (int jetpackResult :
AuthenticationResult.Companion
- .getBiometricFrameworkToJetpackResultMap$credentials_debug().values()) {
+ .getBiometricFrameworkToJetpackResultMap$credentials_release().values()) {
BiometricPromptResult biometricPromptResult =
new BiometricPromptResult(new AuthenticationResult(jetpackResult));
Intent intent = prepareIntentWithGetRequest(GET_CREDENTIAL_REQUEST,
@@ -118,10 +118,10 @@
public void test_retrieveProviderCreateCredReqWithSuccessBpAuthFramework_resultConverted() {
for (int frameworkResult :
AuthenticationResult.Companion
- .getBiometricFrameworkToJetpackResultMap$credentials_debug().keySet()) {
+ .getBiometricFrameworkToJetpackResultMap$credentials_release().keySet()) {
BiometricPromptResult biometricPromptResult =
new BiometricPromptResult(
- AuthenticationResult.Companion.createFrom$credentials_debug(
+ AuthenticationResult.Companion.createFrom$credentials_release(
frameworkResult,
/*isFrameworkBiometricPrompt=*/true
));
@@ -129,7 +129,7 @@
TestUtilsKt.setUpCreatePasswordRequest();
int expectedResult =
AuthenticationResult.Companion
- .getBiometricFrameworkToJetpackResultMap$credentials_debug()
+ .getBiometricFrameworkToJetpackResultMap$credentials_release()
.get(frameworkResult);
Intent intent = prepareIntentWithCreateRequest(
request,
@@ -150,16 +150,16 @@
public void test_retrieveProviderGetCredReqWithSuccessBpAuthFramework_resultConverted() {
for (int frameworkResult :
AuthenticationResult.Companion
- .getBiometricFrameworkToJetpackResultMap$credentials_debug().keySet()) {
+ .getBiometricFrameworkToJetpackResultMap$credentials_release().keySet()) {
BiometricPromptResult biometricPromptResult =
new BiometricPromptResult(
- AuthenticationResult.Companion.createFrom$credentials_debug(
+ AuthenticationResult.Companion.createFrom$credentials_release(
frameworkResult,
/*isFrameworkBiometricPrompt=*/true
));
int expectedResult =
AuthenticationResult.Companion
- .getBiometricFrameworkToJetpackResultMap$credentials_debug()
+ .getBiometricFrameworkToJetpackResultMap$credentials_release()
.get(frameworkResult);
Intent intent = prepareIntentWithGetRequest(GET_CREDENTIAL_REQUEST,
biometricPromptResult);
@@ -180,7 +180,7 @@
public void test_retrieveProviderCreateCredReqWithFailureBpAuthJetpack_retrieveJetpackError() {
for (int jetpackError :
AuthenticationError.Companion
- .getBiometricFrameworkToJetpackErrorMap$credentials_debug().values()) {
+ .getBiometricFrameworkToJetpackErrorMap$credentials_release().values()) {
BiometricPromptResult biometricPromptResult =
new BiometricPromptResult(
new AuthenticationError(
@@ -207,7 +207,7 @@
public void test_retrieveProviderGetCredReqWithFailureBpAuthJetpack_retrieveJetpackError() {
for (int jetpackError :
AuthenticationError.Companion
- .getBiometricFrameworkToJetpackErrorMap$credentials_debug().values()) {
+ .getBiometricFrameworkToJetpackErrorMap$credentials_release().values()) {
BiometricPromptResult biometricPromptResult = new BiometricPromptResult(
new AuthenticationError(
jetpackError,
@@ -232,10 +232,10 @@
public void test_retrieveProviderCreateCredReqWithFailureBpAuthFramework_errorConverted() {
for (int frameworkError :
AuthenticationError.Companion
- .getBiometricFrameworkToJetpackErrorMap$credentials_debug().keySet()) {
+ .getBiometricFrameworkToJetpackErrorMap$credentials_release().keySet()) {
BiometricPromptResult biometricPromptResult =
new BiometricPromptResult(
- AuthenticationError.Companion.createFrom$credentials_debug(
+ AuthenticationError.Companion.createFrom$credentials_release(
frameworkError, BIOMETRIC_AUTHENTICATOR_ERROR_MSG,
/*isFrameworkBiometricPrompt=*/true
));
@@ -243,7 +243,7 @@
TestUtilsKt.setUpCreatePasswordRequest();
int expectedErrorCode =
AuthenticationError.Companion
- .getBiometricFrameworkToJetpackErrorMap$credentials_debug()
+ .getBiometricFrameworkToJetpackErrorMap$credentials_release()
.get(frameworkError);
Intent intent = prepareIntentWithCreateRequest(
request, biometricPromptResult);
@@ -264,9 +264,9 @@
public void test_retrieveProviderGetCredReqWithFailureBpAuthFramework_correctlyConvertedErr() {
for (int frameworkError :
AuthenticationError.Companion
- .getBiometricFrameworkToJetpackErrorMap$credentials_debug().keySet()) {
+ .getBiometricFrameworkToJetpackErrorMap$credentials_release().keySet()) {
BiometricPromptResult biometricPromptResult = new BiometricPromptResult(
- AuthenticationError.Companion.createFrom$credentials_debug(
+ AuthenticationError.Companion.createFrom$credentials_release(
frameworkError, BIOMETRIC_AUTHENTICATOR_ERROR_MSG,
/*isFrameworkBiometricPrompt=*/true
));
@@ -274,7 +274,7 @@
biometricPromptResult);
int expectedErrorCode =
AuthenticationError.Companion
- .getBiometricFrameworkToJetpackErrorMap$credentials_debug()
+ .getBiometricFrameworkToJetpackErrorMap$credentials_release()
.get(frameworkError);
ProviderGetCredentialRequest retrievedRequest = PendingIntentHandler
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreateRestoreCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreateRestoreCredentialRequest.kt
index d650000..8b4929d 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreateRestoreCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreateRestoreCredentialRequest.kt
@@ -33,7 +33,8 @@
* 2.2 and above. If the cloud backup is not enabled, catch the [E2eeUnavailableException] and retry
* without cloud backup.
*
- * @param requestJson the request in JSON format in the standard webauthn web json
+ * @param requestJson the request in JSON format in the
+ * [standard webauthn web json](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson).
* @param isCloudBackupEnabled whether the credential should be backed up to cloud.
* @throws E2eeUnavailableException if [isCloudBackupEnabled] was requested but the user device did
* not enable backup or e2ee (screen lock).
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreateRestoreCredentialResponse.kt b/credentials/credentials/src/main/java/androidx/credentials/CreateRestoreCredentialResponse.kt
index dcf9f2f..a2ed952 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreateRestoreCredentialResponse.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreateRestoreCredentialResponse.kt
@@ -17,21 +17,29 @@
package androidx.credentials
import android.os.Bundle
+import androidx.annotation.RestrictTo
import androidx.credentials.exceptions.CreateCredentialUnknownException
/**
* A response of the [RestoreCredential] flow.
*
- * @property responseJson the public key credential registration response in JSON format.
+ * @property responseJson the public key credential registration response in
+ * [JSON format](https://w3c.github.io/webauthn/#authenticatorattestationresponse).
*/
-class CreateRestoreCredentialResponse(
+class CreateRestoreCredentialResponse
+private constructor(
val responseJson: String,
data: Bundle,
) : CreateCredentialResponse(RestoreCredential.TYPE_RESTORE_CREDENTIAL, data) {
+
+ /** Constructs a [CreateRestoreCredentialResponse]. */
+ constructor(responseJson: String) : this(responseJson, toBundle(responseJson))
+
companion object {
const val BUNDLE_KEY_CREATE_RESTORE_CREDENTIAL_RESPONSE =
"androidx.credentials.BUNDLE_KEY_CREATE_RESTORE_CREDENTIAL_RESPONSE"
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@JvmStatic
fun createFrom(data: Bundle): CreateRestoreCredentialResponse {
val responseJson =
@@ -41,5 +49,12 @@
)
return CreateRestoreCredentialResponse(responseJson, data)
}
+
+ @JvmStatic
+ internal fun toBundle(responseJson: String): Bundle {
+ val bundle = Bundle()
+ bundle.putString(BUNDLE_KEY_CREATE_RESTORE_CREDENTIAL_RESPONSE, responseJson)
+ return bundle
+ }
}
}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/Credential.kt b/credentials/credentials/src/main/java/androidx/credentials/Credential.kt
index ff80871..6f20564 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/Credential.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/Credential.kt
@@ -30,6 +30,7 @@
* [PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL] for `PublicKeyCredential`)
* @property data the credential data in the [Bundle] format
*/
+@OptIn(ExperimentalDigitalCredentialApi::class)
abstract class Credential
internal constructor(
val type: String,
@@ -56,6 +57,7 @@
PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
PublicKeyCredential.createFrom(data)
RestoreCredential.TYPE_RESTORE_CREDENTIAL -> RestoreCredential.createFrom(data)
+ DigitalCredential.TYPE_DIGITAL_CREDENTIAL -> DigitalCredential.createFrom(data)
else -> throw FrameworkClassParsingException()
}
} catch (e: FrameworkClassParsingException) {
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
index 92e17e4..89d293c 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
@@ -63,6 +63,7 @@
* credential selector, with less precedence than account ordering but more precedence than last
* used time; see [PriorityHints] for more information
*/
+@OptIn(ExperimentalDigitalCredentialApi::class)
abstract class CredentialOption
internal constructor(
val type: String,
@@ -180,6 +181,13 @@
)
else -> throw FrameworkClassParsingException()
}
+ DigitalCredential.TYPE_DIGITAL_CREDENTIAL ->
+ GetDigitalCredentialOption.createFrom(
+ requestData = requestData,
+ candidateQueryData = candidateQueryData,
+ requireSystemProvider = requireSystemProvider,
+ allowedProviders = allowedProviders,
+ )
else -> throw FrameworkClassParsingException()
}
} catch (e: FrameworkClassParsingException) {
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt
index ad97445..302316c 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt
@@ -25,6 +25,7 @@
import androidx.annotation.VisibleForTesting
/** Factory that returns the credential provider to be used by Credential Manager. */
+@OptIn(ExperimentalDigitalCredentialApi::class)
internal class CredentialProviderFactory(val context: Context) {
@set:VisibleForTesting
@@ -77,7 +78,13 @@
return tryCreatePreUOemProvider()
} else if (request is GetCredentialRequest) {
for (option in request.credentialOptions) {
- if (option is GetRestoreCredentialOption) {
+ if (option is GetRestoreCredentialOption || option is GetDigitalCredentialOption) {
+ if (request.credentialOptions.any { it !is GetDigitalCredentialOption }) {
+ throw IllegalArgumentException(
+ "`GetDigitalCredentialOption` cannot be" +
+ " combined with other option types in a single request"
+ )
+ }
return tryCreatePreUOemProvider()
}
}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/DigitalCredential.kt b/credentials/credentials/src/main/java/androidx/credentials/DigitalCredential.kt
new file mode 100644
index 0000000..d4d9bd9
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/DigitalCredential.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2024 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.credentials
+
+import android.os.Bundle
+import androidx.credentials.internal.FrameworkClassParsingException
+import androidx.credentials.internal.RequestValidationHelper
+
+/**
+ * Represents the user's digital credential, generally used for verification or sign-in purposes.
+ *
+ * @property credentialJson the digital credential in the JSON format; the latest format is defined
+ * at https://wicg.github.io/digital-credentials/#the-digitalcredential-interface
+ */
+@ExperimentalDigitalCredentialApi
+class DigitalCredential
+private constructor(
+ val credentialJson: String,
+ data: Bundle,
+) : Credential(TYPE_DIGITAL_CREDENTIAL, data) {
+
+ init {
+ require(RequestValidationHelper.isValidJSON(credentialJson)) {
+ "credentialJson must not be empty, and must be a valid JSON"
+ }
+ }
+
+ /**
+ * Constructs a `DigitalCredential`.
+ *
+ * @param credentialJson the digital credential in the JSON format; the latest format is defined
+ * at https://wicg.github.io/digital-credentials/#the-digitalcredential-interface
+ * @throws IllegalArgumentException if the `credentialJson` is not a valid json
+ */
+ constructor(
+ credentialJson: String,
+ ) : this(credentialJson, toBundle(credentialJson))
+
+ /** Companion constants / helpers for [DigitalCredential]. */
+ companion object {
+ /** The type value for public key credential related operations. */
+ const val TYPE_DIGITAL_CREDENTIAL: String = "androidx.credentials.TYPE_DIGITAL_CREDENTIAL"
+
+ internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+
+ @JvmStatic
+ internal fun createFrom(data: Bundle): DigitalCredential {
+ try {
+ val credentialJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
+ return DigitalCredential(credentialJson!!, data)
+ } catch (e: Exception) {
+ throw FrameworkClassParsingException()
+ }
+ }
+
+ @JvmStatic
+ internal fun toBundle(responseJson: String): Bundle {
+ val bundle = Bundle()
+ bundle.putString(BUNDLE_KEY_REQUEST_JSON, responseJson)
+ return bundle
+ }
+ }
+}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt b/credentials/credentials/src/main/java/androidx/credentials/ExperimentalDigitalCredentialApi.kt
similarity index 65%
copy from tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt
copy to credentials/credentials/src/main/java/androidx/credentials/ExperimentalDigitalCredentialApi.kt
index 3fa6028..c1fb83f 100644
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/ExperimentalDigitalCredentialApi.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 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.
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package androidx.tv.integration.presentation
+package androidx.credentials
-import android.content.res.AssetManager
-
-fun AssetManager.readAssetsFile(fileName: String): String =
- open(fileName).bufferedReader().use { it.readText() }
+@RequiresOptIn(
+ "This CredentialManager API is experimental and is likely to change or to be removed in the future."
+)
+@Retention(AnnotationRetention.BINARY)
+annotation class ExperimentalDigitalCredentialApi
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetDigitalCredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/GetDigitalCredentialOption.kt
new file mode 100644
index 0000000..b7b6c62
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetDigitalCredentialOption.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2024 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.credentials
+
+import android.content.ComponentName
+import android.os.Bundle
+import androidx.credentials.internal.FrameworkClassParsingException
+import androidx.credentials.internal.RequestValidationHelper
+
+/**
+ * A request to retrieve the user's digital credential, normally used for verification or sign-in
+ * purpose.
+ *
+ * Note that this option cannot be combined with other types of options in a single
+ * [GetCredentialRequest].
+ *
+ * @property requestJson the request in the JSON format; the latest format is defined at
+ * https://wicg.github.io/digital-credentials/#the-digitalcredentialrequestoptions-dictionary
+ */
+@ExperimentalDigitalCredentialApi
+class GetDigitalCredentialOption
+internal constructor(
+ val requestJson: String,
+ requestData: Bundle,
+ candidateQueryData: Bundle,
+ isSystemProviderRequired: Boolean,
+ isAutoSelectAllowed: Boolean,
+ allowedProviders: Set<ComponentName>,
+ typePriorityHint: @PriorityHints Int,
+) :
+ CredentialOption(
+ type = DigitalCredential.TYPE_DIGITAL_CREDENTIAL,
+ requestData = requestData,
+ candidateQueryData = candidateQueryData,
+ isSystemProviderRequired = isSystemProviderRequired,
+ isAutoSelectAllowed = isAutoSelectAllowed,
+ allowedProviders = allowedProviders,
+ typePriorityHint = typePriorityHint,
+ ) {
+
+ init {
+ require(RequestValidationHelper.isValidJSON(requestJson)) {
+ "credentialJson must not be empty, and must be a valid JSON"
+ }
+ }
+
+ /**
+ * Constructs a `GetDigitalCredentialOption`.
+ *
+ * Note that this option cannot be combined with other types of options in a single
+ * [GetCredentialRequest].
+ *
+ * @param requestJson the request in the JSON format; the latest format is defined at
+ * https://wicg.github.io/digital-credentials/#the-digitalcredentialrequestoptions-dictionary
+ * @throws IllegalArgumentException if the `credentialJson` is not a valid json
+ */
+ constructor(
+ requestJson: String
+ ) : this(
+ requestJson = requestJson,
+ requestData = toBundle(requestJson),
+ candidateQueryData = toBundle(requestJson),
+ isSystemProviderRequired = false,
+ isAutoSelectAllowed = false,
+ allowedProviders = emptySet(),
+ typePriorityHint = PRIORITY_PASSKEY_OR_SIMILAR,
+ )
+
+ /** Companion constants / helpers for [GetDigitalCredentialOption]. */
+ internal companion object {
+ internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+
+ @JvmStatic
+ internal fun toBundle(requestJson: String): Bundle {
+ val bundle = Bundle()
+ bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
+ return bundle
+ }
+
+ @JvmStatic
+ internal fun createFrom(
+ requestData: Bundle,
+ candidateQueryData: Bundle,
+ requireSystemProvider: Boolean,
+ allowedProviders: Set<ComponentName>,
+ ): GetDigitalCredentialOption {
+ try {
+ val requestJson = requestData.getString(BUNDLE_KEY_REQUEST_JSON)!!
+ return GetDigitalCredentialOption(
+ requestJson = requestJson,
+ requestData = requestData,
+ candidateQueryData = candidateQueryData,
+ isSystemProviderRequired = requireSystemProvider,
+ isAutoSelectAllowed =
+ requestData.getBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, false),
+ allowedProviders = allowedProviders,
+ typePriorityHint =
+ requestData.getInt(
+ BUNDLE_KEY_TYPE_PRIORITY_VALUE,
+ PRIORITY_PASSKEY_OR_SIMILAR
+ ),
+ )
+ } catch (e: Exception) {
+ throw FrameworkClassParsingException()
+ }
+ }
+ }
+}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/internal/ConversionUtils.kt b/credentials/credentials/src/main/java/androidx/credentials/internal/ConversionUtils.kt
index f0bee73..d120681 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/internal/ConversionUtils.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/internal/ConversionUtils.kt
@@ -74,10 +74,8 @@
return createCredentialData
}
-internal fun toJetpackGetException(
- errorType: String,
- errorMsg: CharSequence?
-): GetCredentialException {
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+fun toJetpackGetException(errorType: String, errorMsg: CharSequence?): GetCredentialException {
return when (errorType) {
android.credentials.GetCredentialException.TYPE_NO_CREDENTIAL ->
diff --git a/datastore/datastore-core/build.gradle b/datastore/datastore-core/build.gradle
index f184764..d509e6a 100644
--- a/datastore/datastore-core/build.gradle
+++ b/datastore/datastore-core/build.gradle
@@ -28,8 +28,6 @@
plugins {
id("AndroidXPlugin")
id("com.android.library")
- id("com.google.protobuf")
- id ("kotlin-parcelize")
}
android {
@@ -42,26 +40,6 @@
namespace "androidx.datastore.core"
}
-protobuf {
- protoc {
- artifact = libs.protobufCompiler.get()
- }
- generateProtoTasks {
- all().each { task ->
- task.builtins {
- java {
- option "lite"
- }
- }
- }
- }
-}
-
-def protoDir = project.layout.projectDirectory.dir("src/androidInstrumentedTest/proto")
-tasks.named("extractAndroidTestProto").configure {
- it.inputFiles.from(project.files(protoDir))
-}
-
androidXMultiplatform {
jvm()
mac()
@@ -137,7 +115,6 @@
androidInstrumentedTest {
dependsOn(commonJvmTest)
dependencies {
- implementation(libs.protobufLite)
implementation(libs.truth)
implementation(project(":internal-testutils-truth"))
implementation(libs.testRunner)
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/AndroidManifest.xml b/datastore/datastore-core/src/androidInstrumentedTest/AndroidManifest.xml
index c25da00..4ec52f2f 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/AndroidManifest.xml
+++ b/datastore/datastore-core/src/androidInstrumentedTest/AndroidManifest.xml
@@ -15,16 +15,5 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
- <application>
- <service android:name="androidx.datastore.core.twoWayIpc.TwoWayIpcService"
- android:enabled="true"
- android:exported="false"
- android:process=":TwoWayIpcService" />
- <service android:name="androidx.datastore.core.twoWayIpc.TwoWayIpcService2"
- android:enabled="true"
- android:exported="false"
- android:process=":TwoWayIpcService2" />
- </application>
-
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
diff --git a/datastore/datastore-preferences-core/build.gradle b/datastore/datastore-preferences-core/build.gradle
index efa4b1f..9dfab0a 100644
--- a/datastore/datastore-preferences-core/build.gradle
+++ b/datastore/datastore-preferences-core/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -118,4 +120,5 @@
description = "Android Preferences DataStore without the Android Dependencies"
legacyDisableKotlinStrictApiMode = true
metalavaK2UastEnabled = false
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/datastore/datastore-preferences-rxjava2/build.gradle b/datastore/datastore-preferences-rxjava2/build.gradle
index aef9070..30c019c 100644
--- a/datastore/datastore-preferences-rxjava2/build.gradle
+++ b/datastore/datastore-preferences-rxjava2/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -68,4 +70,5 @@
inceptionYear = "2020"
description = "Android DataStore Core - contains wrappers for using DataStore using RxJava2"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/datastore/datastore-preferences-rxjava3/build.gradle b/datastore/datastore-preferences-rxjava3/build.gradle
index d168af4..b9208ea 100644
--- a/datastore/datastore-preferences-rxjava3/build.gradle
+++ b/datastore/datastore-preferences-rxjava3/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -68,4 +70,5 @@
inceptionYear = "2020"
description = "Android DataStore Core - contains wrappers for using DataStore using RxJava2"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/datastore/datastore-preferences/build.gradle b/datastore/datastore-preferences/build.gradle
index 1bf3287..9fb7310 100644
--- a/datastore/datastore-preferences/build.gradle
+++ b/datastore/datastore-preferences/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
@@ -98,4 +100,5 @@
inceptionYear = "2020"
description = "Android Preferences DataStore"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/datastore/integration-tests/testapp/build.gradle b/datastore/integration-tests/testapp/build.gradle
new file mode 100644
index 0000000..8266634
--- /dev/null
+++ b/datastore/integration-tests/testapp/build.gradle
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 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.
+ */
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+ id("com.google.protobuf")
+ id ("kotlin-parcelize")
+}
+
+android {
+ namespace "androidx.datastore.testapp"
+}
+
+protobuf {
+ protoc {
+ artifact = libs.protobufCompiler.get()
+ }
+ generateProtoTasks {
+ all().each { task ->
+ task.builtins {
+ java {
+ option "lite"
+ }
+ }
+ }
+ }
+}
+
+dependencies {
+ implementation(libs.okio)
+ implementation(libs.protobufLite)
+ implementation("androidx.lifecycle:lifecycle-service:2.6.1")
+ implementation(project(":datastore:datastore-core"))
+ implementation(project(":datastore:datastore-core-okio"))
+
+ androidTestImplementation(libs.kotlinCoroutinesTest)
+ androidTestImplementation(libs.kotlinTest)
+ androidTestImplementation(libs.truth)
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(project(":internal-testutils-truth"))
+ androidTestImplementation(project(":internal-testutils-datastore"))
+ androidTestImplementation(project(":kruth:kruth"))
+}
diff --git a/datastore/integration-tests/testapp/src/androidTest/AndroidManifest.xml b/datastore/integration-tests/testapp/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..867fda8
--- /dev/null
+++ b/datastore/integration-tests/testapp/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2024 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">
+ <application>
+ <service android:name="androidx.datastore.testapp.twoWayIpc.TwoWayIpcService"
+ android:enabled="true"
+ android:exported="false"
+ android:process=":TwoWayIpcService" />
+ <service android:name="androidx.datastore.testapp.twoWayIpc.TwoWayIpcService2"
+ android:enabled="true"
+ android:exported="false"
+ android:process=":TwoWayIpcService2" />
+ </application>
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+</manifest>
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/InterProcessCompletableTest.kt b/datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/InterProcessCompletableTest.kt
similarity index 89%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/InterProcessCompletableTest.kt
rename to datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/InterProcessCompletableTest.kt
index 9623f58..c6233a5 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/InterProcessCompletableTest.kt
+++ b/datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/InterProcessCompletableTest.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package androidx.datastore.core.multiprocess
+package androidx.datastore.testapp.multiprocess
import android.os.Parcelable
-import androidx.datastore.core.twoWayIpc.InterProcessCompletable
-import androidx.datastore.core.twoWayIpc.IpcAction
-import androidx.datastore.core.twoWayIpc.IpcUnit
-import androidx.datastore.core.twoWayIpc.TwoWayIpcSubject
+import androidx.datastore.testapp.twoWayIpc.InterProcessCompletable
+import androidx.datastore.testapp.twoWayIpc.IpcAction
+import androidx.datastore.testapp.twoWayIpc.IpcUnit
+import androidx.datastore.testapp.twoWayIpc.TwoWayIpcSubject
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.async
import kotlinx.coroutines.yield
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultiProcessDataStoreIpcTest.kt b/datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/MultiProcessDataStoreIpcTest.kt
similarity index 96%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultiProcessDataStoreIpcTest.kt
rename to datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/MultiProcessDataStoreIpcTest.kt
index 86887fc..b13ee2d 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultiProcessDataStoreIpcTest.kt
+++ b/datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/MultiProcessDataStoreIpcTest.kt
@@ -14,21 +14,24 @@
* limitations under the License.
*/
-package androidx.datastore.core.multiprocess
+// Parcelize object is testing internal implementation of datastore-core library
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER")
+
+package androidx.datastore.testapp.multiprocess
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.CorruptionHandler
import androidx.datastore.core.IOException
import androidx.datastore.core.SharedCounter
-import androidx.datastore.core.multiprocess.ipcActions.ReadTextAction
-import androidx.datastore.core.multiprocess.ipcActions.SetTextAction
-import androidx.datastore.core.multiprocess.ipcActions.StorageVariant
-import androidx.datastore.core.multiprocess.ipcActions.createMultiProcessTestDatastore
-import androidx.datastore.core.multiprocess.ipcActions.datastore
-import androidx.datastore.core.twoWayIpc.InterProcessCompletable
-import androidx.datastore.core.twoWayIpc.IpcAction
-import androidx.datastore.core.twoWayIpc.IpcUnit
-import androidx.datastore.core.twoWayIpc.TwoWayIpcSubject
+import androidx.datastore.testapp.multiprocess.ipcActions.ReadTextAction
+import androidx.datastore.testapp.multiprocess.ipcActions.SetTextAction
+import androidx.datastore.testapp.multiprocess.ipcActions.StorageVariant
+import androidx.datastore.testapp.multiprocess.ipcActions.createMultiProcessTestDatastore
+import androidx.datastore.testapp.multiprocess.ipcActions.datastore
+import androidx.datastore.testapp.twoWayIpc.InterProcessCompletable
+import androidx.datastore.testapp.twoWayIpc.IpcAction
+import androidx.datastore.testapp.twoWayIpc.IpcUnit
+import androidx.datastore.testapp.twoWayIpc.TwoWayIpcSubject
import androidx.datastore.testing.TestMessageProto.FooProto
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CompletableDeferred
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultiProcessTestRule.kt b/datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/MultiProcessTestRule.kt
similarity index 92%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultiProcessTestRule.kt
rename to datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/MultiProcessTestRule.kt
index c2cbb5b..82df64d 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultiProcessTestRule.kt
+++ b/datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/MultiProcessTestRule.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package androidx.datastore.core.multiprocess
+package androidx.datastore.testapp.multiprocess
-import androidx.datastore.core.twoWayIpc.TwoWayIpcConnection
-import androidx.datastore.core.twoWayIpc.TwoWayIpcService
-import androidx.datastore.core.twoWayIpc.TwoWayIpcService2
+import androidx.datastore.testapp.twoWayIpc.TwoWayIpcConnection
+import androidx.datastore.testapp.twoWayIpc.TwoWayIpcService
+import androidx.datastore.testapp.twoWayIpc.TwoWayIpcService2
import androidx.test.platform.app.InstrumentationRegistry
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.time.Duration.Companion.seconds
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultipleDataStoresInMultipleProcessesTest.kt b/datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/MultipleDataStoresInMultipleProcessesTest.kt
similarity index 91%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultipleDataStoresInMultipleProcessesTest.kt
rename to datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/MultipleDataStoresInMultipleProcessesTest.kt
index 00442d7..9b07814 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultipleDataStoresInMultipleProcessesTest.kt
+++ b/datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/MultipleDataStoresInMultipleProcessesTest.kt
@@ -14,19 +14,22 @@
* limitations under the License.
*/
-package androidx.datastore.core.multiprocess
+// Parcelize object is testing internal implementation of datastore-core library
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
-import androidx.datastore.core.multiprocess.ipcActions.ReadTextAction
-import androidx.datastore.core.multiprocess.ipcActions.SetTextAction
-import androidx.datastore.core.multiprocess.ipcActions.StorageVariant
-import androidx.datastore.core.multiprocess.ipcActions.createMultiProcessTestDatastore
-import androidx.datastore.core.multiprocess.ipcActions.datastore
-import androidx.datastore.core.twoWayIpc.CompositeServiceSubjectModel
-import androidx.datastore.core.twoWayIpc.InterProcessCompletable
-import androidx.datastore.core.twoWayIpc.IpcAction
-import androidx.datastore.core.twoWayIpc.IpcUnit
-import androidx.datastore.core.twoWayIpc.SubjectReadWriteProperty
-import androidx.datastore.core.twoWayIpc.TwoWayIpcSubject
+package androidx.datastore.testapp.multiprocess
+
+import androidx.datastore.testapp.multiprocess.ipcActions.ReadTextAction
+import androidx.datastore.testapp.multiprocess.ipcActions.SetTextAction
+import androidx.datastore.testapp.multiprocess.ipcActions.StorageVariant
+import androidx.datastore.testapp.multiprocess.ipcActions.createMultiProcessTestDatastore
+import androidx.datastore.testapp.multiprocess.ipcActions.datastore
+import androidx.datastore.testapp.twoWayIpc.CompositeServiceSubjectModel
+import androidx.datastore.testapp.twoWayIpc.InterProcessCompletable
+import androidx.datastore.testapp.twoWayIpc.IpcAction
+import androidx.datastore.testapp.twoWayIpc.IpcUnit
+import androidx.datastore.testapp.twoWayIpc.SubjectReadWriteProperty
+import androidx.datastore.testapp.twoWayIpc.TwoWayIpcSubject
import androidx.datastore.testing.TestMessageProto.FooProto
import androidx.kruth.assertThat
import kotlin.time.Duration.Companion.seconds
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/TwoWayIpcTest.kt b/datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/TwoWayIpcTest.kt
similarity index 95%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/TwoWayIpcTest.kt
rename to datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/TwoWayIpcTest.kt
index 3cf76e5..a6d77b6 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/TwoWayIpcTest.kt
+++ b/datastore/integration-tests/testapp/src/androidTest/java/androidx/datastore/testapp/multiprocess/TwoWayIpcTest.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package androidx.datastore.core.multiprocess
+package androidx.datastore.testapp.multiprocess
import android.os.Parcelable
-import androidx.datastore.core.twoWayIpc.CompositeServiceSubjectModel
-import androidx.datastore.core.twoWayIpc.IpcAction
-import androidx.datastore.core.twoWayIpc.TwoWayIpcSubject
+import androidx.datastore.testapp.twoWayIpc.CompositeServiceSubjectModel
+import androidx.datastore.testapp.twoWayIpc.IpcAction
+import androidx.datastore.testapp.twoWayIpc.TwoWayIpcSubject
import com.google.common.truth.Truth.assertThat
import kotlinx.parcelize.Parcelize
import org.junit.Rule
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/ProtoOkioSerializer.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/ProtoOkioSerializer.kt
similarity index 95%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/ProtoOkioSerializer.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/ProtoOkioSerializer.kt
index cb319f1..6a291b8 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/ProtoOkioSerializer.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/ProtoOkioSerializer.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package androidx.datastore.core
+package androidx.datastore.testapp
+import androidx.datastore.core.CorruptionException
import androidx.datastore.core.okio.OkioSerializer
import com.google.protobuf.ExtensionRegistryLite
import com.google.protobuf.InvalidProtocolBufferException
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/ProtoSerializer.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/ProtoSerializer.kt
similarity index 92%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/ProtoSerializer.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/ProtoSerializer.kt
index 587f24f..f9eb2e3 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/ProtoSerializer.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/ProtoSerializer.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package androidx.datastore.core
+package androidx.datastore.testapp
+import androidx.datastore.core.CorruptionException
+import androidx.datastore.core.Serializer
import com.google.protobuf.ExtensionRegistryLite
import com.google.protobuf.InvalidProtocolBufferException
import com.google.protobuf.MessageLite
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/CreateDatastoreAction.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/multiprocess/ipcActions/CreateDatastoreAction.kt
similarity index 87%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/CreateDatastoreAction.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/multiprocess/ipcActions/CreateDatastoreAction.kt
index 4890be9..cb3ef18 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/CreateDatastoreAction.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/multiprocess/ipcActions/CreateDatastoreAction.kt
@@ -14,24 +14,28 @@
* limitations under the License.
*/
-package androidx.datastore.core.multiprocess.ipcActions
+// Parcelize object is testing internal implementation of datastore-core library
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+package androidx.datastore.testapp.multiprocess.ipcActions
+
+import android.annotation.SuppressLint
import android.os.Parcelable
import androidx.datastore.core.CorruptionHandler
import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreImpl
import androidx.datastore.core.FileStorage
import androidx.datastore.core.MultiProcessCoordinator
-import androidx.datastore.core.ProtoOkioSerializer
-import androidx.datastore.core.ProtoSerializer
import androidx.datastore.core.Serializer
import androidx.datastore.core.handlers.NoOpCorruptionHandler
import androidx.datastore.core.okio.OkioSerializer
import androidx.datastore.core.okio.OkioStorage
-import androidx.datastore.core.twoWayIpc.CompositeServiceSubjectModel
-import androidx.datastore.core.twoWayIpc.IpcAction
-import androidx.datastore.core.twoWayIpc.SubjectReadWriteProperty
-import androidx.datastore.core.twoWayIpc.TwoWayIpcSubject
+import androidx.datastore.testapp.ProtoOkioSerializer
+import androidx.datastore.testapp.ProtoSerializer
+import androidx.datastore.testapp.twoWayIpc.CompositeServiceSubjectModel
+import androidx.datastore.testapp.twoWayIpc.IpcAction
+import androidx.datastore.testapp.twoWayIpc.SubjectReadWriteProperty
+import androidx.datastore.testapp.twoWayIpc.TwoWayIpcSubject
import androidx.datastore.testing.TestMessageProto.FooProto
import com.google.protobuf.ExtensionRegistryLite
import java.io.File
@@ -117,6 +121,7 @@
)
}
+@SuppressLint("BanParcelableUsage")
@Parcelize
private class CreateDatastoreAction(
private val filePath: String,
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/ReadTextAction.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/multiprocess/ipcActions/ReadTextAction.kt
similarity index 67%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/ReadTextAction.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/multiprocess/ipcActions/ReadTextAction.kt
index eafa6b5..0162238 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/ReadTextAction.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/multiprocess/ipcActions/ReadTextAction.kt
@@ -14,17 +14,23 @@
* limitations under the License.
*/
-package androidx.datastore.core.multiprocess.ipcActions
+// Parcelize object is testing internal implementation of datastore-core library
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+package androidx.datastore.testapp.multiprocess.ipcActions
+
+import android.annotation.SuppressLint
import android.os.Parcelable
-import androidx.datastore.core.twoWayIpc.IpcAction
-import androidx.datastore.core.twoWayIpc.TwoWayIpcSubject
+import androidx.datastore.testapp.twoWayIpc.IpcAction
+import androidx.datastore.testapp.twoWayIpc.TwoWayIpcSubject
import kotlinx.coroutines.flow.first
import kotlinx.parcelize.Parcelize
@Parcelize
internal class ReadTextAction : IpcAction<ReadTextAction.TextValue>() {
- @Parcelize data class TextValue(val value: String) : Parcelable
+ @SuppressLint("BanParcelableUsage")
+ @Parcelize
+ data class TextValue(val value: String) : Parcelable
override suspend fun invokeInRemoteProcess(subject: TwoWayIpcSubject): TextValue {
return TextValue(subject.datastore.data.first().text)
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/SetTextAction.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/multiprocess/ipcActions/SetTextAction.kt
similarity index 73%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/SetTextAction.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/multiprocess/ipcActions/SetTextAction.kt
index ce98deb..929b23f 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/SetTextAction.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/multiprocess/ipcActions/SetTextAction.kt
@@ -14,15 +14,20 @@
* limitations under the License.
*/
-package androidx.datastore.core.multiprocess.ipcActions
+// Parcelize object is testing internal implementation of datastore-core library
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+package androidx.datastore.testapp.multiprocess.ipcActions
+
+import android.annotation.SuppressLint
import android.os.Parcelable
-import androidx.datastore.core.twoWayIpc.InterProcessCompletable
-import androidx.datastore.core.twoWayIpc.IpcAction
-import androidx.datastore.core.twoWayIpc.IpcUnit
-import androidx.datastore.core.twoWayIpc.TwoWayIpcSubject
+import androidx.datastore.testapp.twoWayIpc.InterProcessCompletable
+import androidx.datastore.testapp.twoWayIpc.IpcAction
+import androidx.datastore.testapp.twoWayIpc.IpcUnit
+import androidx.datastore.testapp.twoWayIpc.TwoWayIpcSubject
import kotlinx.parcelize.Parcelize
+@SuppressLint("BanParcelableUsage")
@Parcelize
internal class SetTextAction(
private val value: String,
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/CompositeServiceSubjectModel.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/CompositeServiceSubjectModel.kt
similarity index 94%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/CompositeServiceSubjectModel.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/CompositeServiceSubjectModel.kt
index 3b8dd64..741bb88 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/CompositeServiceSubjectModel.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/CompositeServiceSubjectModel.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package androidx.datastore.core.twoWayIpc
+package androidx.datastore.testapp.twoWayIpc
+//noinspection BanConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap
/**
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/InterProcessCompletable.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/InterProcessCompletable.kt
similarity index 96%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/InterProcessCompletable.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/InterProcessCompletable.kt
index 46eaffa..4e5d506 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/InterProcessCompletable.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/InterProcessCompletable.kt
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-package androidx.datastore.core.twoWayIpc
+package androidx.datastore.testapp.twoWayIpc
+import android.annotation.SuppressLint
import android.os.Parcelable
import java.util.UUID
import kotlinx.coroutines.CompletableDeferred
import kotlinx.parcelize.Parcelize
/** A [Parcelable] [CompletableDeferred] implementation that can be shared across processes. */
+@SuppressLint("BanParcelableUsage")
@Parcelize
internal class InterProcessCompletable<T : Parcelable>(
private val key: String = UUID.randomUUID().toString(),
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/IpcAction.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/IpcAction.kt
similarity index 85%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/IpcAction.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/IpcAction.kt
index fa2de84..086510f 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/IpcAction.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/IpcAction.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package androidx.datastore.core.twoWayIpc
+package androidx.datastore.testapp.twoWayIpc
+import android.annotation.SuppressLint
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@@ -25,4 +26,4 @@
}
/** Utility object for [IpcAction]s that do not return a value. */
-@Parcelize object IpcUnit : Parcelable
+@SuppressLint("BanParcelableUsage") @Parcelize object IpcUnit : Parcelable
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/IpcLogger.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/IpcLogger.kt
similarity index 96%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/IpcLogger.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/IpcLogger.kt
index 628ff8b..278f16d 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/IpcLogger.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/IpcLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.datastore.core.twoWayIpc
+package androidx.datastore.testapp.twoWayIpc
import android.app.Application
import android.os.Build
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcBus.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcBus.kt
similarity index 97%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcBus.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcBus.kt
index 4768068..3e84a15 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcBus.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcBus.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package androidx.datastore.core.twoWayIpc
+package androidx.datastore.testapp.twoWayIpc
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.os.Messenger
-import androidx.datastore.core.twoWayIpc.IpcLogger.log
+import androidx.datastore.testapp.twoWayIpc.IpcLogger.log
import java.util.UUID
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CompletableDeferred
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcConnection.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcConnection.kt
similarity index 98%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcConnection.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcConnection.kt
index e153d5d..d37575e 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcConnection.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcConnection.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.datastore.core.twoWayIpc
+package androidx.datastore.testapp.twoWayIpc
import android.content.ComponentName
import android.content.Context
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcService.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcService.kt
similarity index 95%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcService.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcService.kt
index 9834950..2fb7ec2 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcService.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcService.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.datastore.core.twoWayIpc
+package androidx.datastore.testapp.twoWayIpc
import android.content.Intent
import android.os.Handler
@@ -44,7 +44,7 @@
* It properly scopes those subjects and destroys their scopes when the Service is destroyed,
* allowing tests to properly maintain resources.
*
- * @see androidx.datastore.core.multiprocess.MultiProcessTestRule
+ * @see androidx.datastore.testapp.multiprocess.MultiProcessTestRule
*/
open class TwoWayIpcService : LifecycleService() {
private val subjects = mutableListOf<TwoWayIpcSubject>()
@@ -89,6 +89,7 @@
)
override fun onBind(intent: Intent): IBinder? {
+ super.onBind(intent)
return messenger.binder
}
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcSubject.kt b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcSubject.kt
similarity index 98%
rename from datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcSubject.kt
rename to datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcSubject.kt
index 8c6cd3f..c6689a3 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcSubject.kt
+++ b/datastore/integration-tests/testapp/src/main/java/androidx/datastore/testapp/twoWayIpc/TwoWayIpcSubject.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.datastore.core.twoWayIpc
+package androidx.datastore.testapp.twoWayIpc
import android.os.Bundle
import android.os.Parcelable
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/proto/test.proto b/datastore/integration-tests/testapp/src/main/proto/test.proto
similarity index 100%
rename from datastore/datastore-core/src/androidInstrumentedTest/proto/test.proto
rename to datastore/integration-tests/testapp/src/main/proto/test.proto
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 619c5f9..d44adf5 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -48,8 +48,8 @@
[0-9]+ problem.* found storing the configuration cache.*
See the complete report at file://.*/build/reports/configuration\-cache/[^ ]*/[^ ]*/configuration\-cache\-report\.html
# > Task :compose:ui:ui:processDebugAndroidTestManifest
-\$OUT_DIR/androidx/compose/runtime/runtime\-saveable/build/intermediates/tmp/manifest/androidTest/debug/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR/androidx/compose/ui/ui\-tooling/build/intermediates/tmp/manifest/androidTest/debug/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
+\$OUT_DIR/androidx/compose/runtime/runtime\-saveable/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
+\$OUT_DIR/androidx/compose/ui/ui\-tooling/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
# > Task :buildSrc:build UP-TO-DATE
A fine\-grained performance profile is available\: use the \-\-scan option\.
# > Task :docs
@@ -333,3 +333,5 @@
WARN: Attempt to load key 'java\.correct\.class\.type\.by\.place\.resolve\.scope' for not yet loaded registry
warning: unable to find kotlin\-stdlib\.jar in the Kotlin home directory\. Pass either '\-no\-stdlib' to prevent adding it to the classpath, or the correct '\-kotlin\-home'
warning: unable to find kotlin\-script\-runtime\.jar in the Kotlin home directory\. Pass either '\-no\-stdlib' to prevent adding it to the classpath, or the correct '\-kotlin\-home'
+# develocity plugin begign warning, reported back to Gradle
+Failed sysctl call: hw\.nperflevels, Error code: 2
diff --git a/development/validateRefactor.sh b/development/validateRefactor.sh
index 7170a98..1fb6cd2 100755
--- a/development/validateRefactor.sh
+++ b/development/validateRefactor.sh
@@ -32,13 +32,21 @@
Validates that libraries built from the given versions are the same as
the build outputs built at HEAD. This can be used to validate that a refactor
- did not change the outputs. If a git treeish is given with no path, the path is considered to be frameworks/support
+ did not change the outputs.
+ If a git treeish is given with no path, the path is considered to be frameworks/support
Example: $0 HEAD^
Example: $0 prebuilts/androidx/external:HEAD^ frameworks/support:work^
- * A git treeish is what you type when you run 'git checkout <git treeish>'
+ * A git treeish is what you type when you run 'git checkout <git treeish>'
See also https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddeftree-ishatree-ishalsotreeish .
+
+ You can also supply additional arguments that will be passed through to validateRefactorHelper.py, using -P
+ For example, the baseline arguments that validateRefactorHelper.py accepts.
+ Example: $0 HEAD^ -p agpKmp
+
+ validateRefactor also accepts git treeishes as named arguments using -g
+ Example: $0 -g HEAD^ -p agpKmp
"
return 1
}
@@ -118,27 +126,43 @@
unzipInPlace "${tempOutPath}/dist/docs-public-0.zip"
}
-oldCommits="$(expandCommitArgs $@)"
-projectPaths="$(getParticipatingProjectPaths $oldCommits)"
-if echo $projectPaths | grep external/dokka >/dev/null; then
- if [ "$BUILD_DOKKA" == "" ]; then
- echo "It doesn't make sense to include the external/dokka project without also setting BUILD_DOKKA=true. Did you mean to set BUILD_DOKKA=true?"
- exit 1
+nonNamedArgs=()
+oldCommits=()
+passThruArgs=()
+while [ $OPTIND -le "$#" ]; do
+ if getopts ":p:g:" opt; then
+ case $opt in
+ \? ) usage;;
+ g ) oldCommits+="$(expandCommitArgs $OPTARG)";;
+ p ) passThruArgs+="$OPTARG";;
+ esac
+ case $OPTARG in
+ -*) usage;;
+ esac
+ else
+ nonNamedArgs+=("${!OPTIND}")
+ ((OPTIND++))
fi
-fi
-echo old commits: $oldCommits
+done
+
+oldCommits+="$(expandCommitArgs $nonNamedArgs)"
+
+projectPaths="$(getParticipatingProjectPaths $oldCommits)"
if [ "$oldCommits" == "" ]; then
usage
fi
+
newCommits="$(getCurrentCommits $projectPaths)"
cd "$supportRoot"
+if [[ $(git update-index --refresh) ]]; then echo "You have local changes; stash or commit them or this script won't work"; exit 1; fi
+if [[ $(git diff-index --quiet HEAD) ]]; then echo "You have local changes; stash or commit them or this script won't work"; exit 1; fi
+echo old commits: $oldCommits
echo new commits: $newCommits
-
+cd "$supportRoot"
oldOutPath="${checkoutRoot}/out-old"
newOutPath="${checkoutRoot}/out-new"
tempOutPath="${checkoutRoot}/out"
-
rm -rf "$oldOutPath" "$newOutPath" "$tempOutPath"
echo building new commit
@@ -158,10 +182,9 @@
uncheckout "$projectPaths"
mv "$tempOutPath" "$oldOutPath"
+
echo
echo diffing results
-# Don't care about maven-metadata files because they have timestamps in them
-# We might care to know whether .sha1 or .md5 files have changed, but changes in those files will always be accompanied by more meaningful changes in other files, so we don't need to show changes in .sha1 or .md5 files
-# We also don't care about several specific files, either
-echoAndDo diff -r -x "*.md5*" -x "*.sha*" -x "*maven-metadata.xml" -x buildSrc.jar -x jetpad-integration.jar -x "top-of-tree-m2repository-all-0.zip" -x noto-emoji-compat-java.jar -x versionedparcelable-annotation.jar -x dokkaTipOfTreeDocs-0.zip "$oldOutPath/dist" "$newOutPath/dist"
+# This script performs the diff, and filters out known issues and non-issues with baselines
+python development/validateRefactorHelper.py "$passThruArgs"
echo end of difference
diff --git a/development/validateRefactorHelper.py b/development/validateRefactorHelper.py
new file mode 100644
index 0000000..dcdf3ae
--- /dev/null
+++ b/development/validateRefactorHelper.py
@@ -0,0 +1,197 @@
+#
+# 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.
+#
+"""A helper script for validateRefactor.sh. Should generally not be used directly.
+
+Can be used directly if validateRefactor.sh has already created the out-old & out-new dirs.
+In such a case, it can be run to compare those directories without regenerating them.
+This is generally only useful when updating baselines or iterating on this script itself.
+Takes baseline names as CLI arguments, which may be passed through from validateRefactor.sh.
+
+Typical usage example:
+
+ python validateRefactorHelper.py agpKmp
+"""
+import itertools
+import os
+import shutil
+import subprocess
+import sys
+
+# noto-emoji-compat `bundleinside`s an externally-built with-timestamps jar.
+# classes.jar is compared using `diffuse` instead of unzipping and diffing class files.
+bannedJars = ["-x", "noto-emoji-compat-java.jar", "-x", "classes.jar"]
+# java and json aren"t for unzipping, but the poor exclude-everything-but-jars regex doesn't
+# exclude them. Same for exclude-non-klib and .kt/.knm
+areNotZips = ["-x", r"**\.java", "-x", r"**\.json", "-x", r"**\.kt", "-x", r"**\.knm"]
+# keeps making my regexes fall over :(
+hasNoExtension = ["-x", "manifest", "-x", "module"]
+doNotUnzip = bannedJars + areNotZips + hasNoExtension
+
+def diff(excludes):
+ return popenAndReturn(["diff", "-r", "../../out-old/dist/", "../../out-new/dist/"] + excludes)
+
+def popenAndReturn(args):
+ return subprocess.Popen(args, stdout=subprocess.PIPE).stdout.read().decode("utf-8").split("\n")
+
+# Finds and unzips all files with old/new diff that _do not_ match the argument regex.
+def findFilesMatchingWithDiffAndUnzip(regexThatMatchesEverythingElse):
+ # Exclude all things that are *not* the desired zip type
+ # (because diff doesn"t have an --include, only --exclude).
+ zipsWithDiffs = diff(["-q", "-x", regexThatMatchesEverythingElse] + doNotUnzip)
+ # Take only changed files, not new/deleted ones (the diff there is obvious)
+ zipsWithDiffs = filter(lambda s: s.startswith("Files"), zipsWithDiffs)
+ zipsWithDiffs = map(lambda s: s.split()[1:4:2], zipsWithDiffs)
+ zipsWithDiffs = list(itertools.chain.from_iterable(zipsWithDiffs)) # flatten
+ # And unzip them
+ for filename in zipsWithDiffs:
+ print("unzipping " + filename)
+ # if os.path.exists(filename+".unzipped/"): os.rmdir(filename+".unzipped/")
+ shutil.rmtree(filename+".unzipped/")
+ subprocess.Popen(["unzip", "-qq", "-o", filename, "-d", filename+".unzipped/"])
+
+diffusePath = "../../prebuilts/build-tools/diffuse-0.3.0/bin/diffuse"
+
+def compareWithDiffuse(listOfJars):
+ for jarPath in list(filter(None, listOfJars)):
+ print("jarpath: " + jarPath)
+ newJarPath = jarPath.replace("out-old", "out-new")
+ print(popenAndReturn([diffusePath, "diff", "--jar", jarPath, newJarPath]))
+
+# We might care to know whether .sha1 or .md5 files have changed, but changes in those files will
+# always be accompanied by more meaningful changes in other files, so we don"t need to show changes
+# in .sha1 or .md5 files, or in .module files showing the hashes of other files, or config names.
+excludedHashes = ["-x", "*.md5*", "-x", "*.sha**", "-I", " \"md5\".*", \
+ "-I", " \"sha.*", "-I", " \"size\".*", "-I", " \"name\".*"]
+# Don"t care about maven-metadata files because they have timestamps in them.
+excludedFiles = ["-x", "*maven-metadata.xml**", "-x", r"**\.knm"] # temporarily ignore knms
+# Also, ignore files that we already unzipped
+excludedZips = ["-x", "*.zip", "-x", "*.jar", "-x", "*.aar", "-x", "*.apk", "-x", "*.klib"]
+
+# These are baselined changes that we understand and know are no-ops in refactors
+# "Unskippable" changes are multi-line and can't be skipped in `diff`, so post-process
+baselinedChangesForAgpKmp = [
+ # these are new attributes being added
+ """ "org.gradle.libraryelements": "aar",""",
+ """ "org.gradle.jvm.environment": "android",""",
+ """ "org.gradle.jvm.environment": "non-jvm",""",
+ """ "org.gradle.jvm.environment": "standard-jvm",""",
+ # this attribute swap occurs alongside the above new attributes added.
+ # https://chat.google.com/room/AAAAW8qmCIs/4phaNn_gsrc
+ """ "org.jetbrains.kotlin.platform.type": "androidJvm\"""",
+ """ "org.jetbrains.kotlin.platform.type": "jvm\"""",
+ # name-only change; nothing resolves based on names
+ """ "name": "releaseApiElements-published",""",
+ """ "name": "androidApiElements-published",""",
+ """ <pre>actual typealias""", # open bug in dackka b/339221337
+ # we are switching from our KMP sourcejars solution to the upstream one
+ """ "org.gradle.docstype": "fake-sources",""",
+ """ "org.gradle.docstype": "sources",""",
+]
+unskippableBaselinedChangesForAgpKmp = [
+ """
+< },
+< "excludes": [
+< {
+< "group": "org.jetbrains.kotlin",
+< "module": "kotlin-stdlib-common"
+< },
+< {
+< "group": "org.jetbrains.kotlin",
+< "module": "kotlin-test-common"
+< },
+< {
+< "group": "org.jetbrains.kotlin",
+< "module": "kotlin-test-annotations-common"
+< }
+< ]
+---
+> }
+""",
+"""
+< <exclusions>
+< <exclusion>
+< <groupId>org.jetbrains.kotlin</groupId>
+< <artifactId>kotlin-stdlib-common</artifactId>
+< </exclusion>
+< <exclusion>
+< <groupId>org.jetbrains.kotlin</groupId>
+< <artifactId>kotlin-test-common</artifactId>
+< </exclusion>
+< <exclusion>
+< <groupId>org.jetbrains.kotlin</groupId>
+< <artifactId>kotlin-test-annotations-common</artifactId>
+< </exclusion>
+< </exclusions>
+"""
+]
+
+baselinedChanges = []
+unskippableBaselinedChanges = []
+arguments = sys.argv[1:]
+if "agpKmp" in arguments:
+ arguments.remove("agpKmp")
+ print("IGNORING DIFF FOR agpKmp")
+ baselinedChanges += baselinedChangesForAgpKmp
+ unskippableBaselinedChanges += unskippableBaselinedChangesForAgpKmp
+if arguments:
+ print("invalid argument(s) for validateRefactorHelper: " + ", ".join(arguments))
+ print("currently recognized arguments: agpKmp")
+ exit()
+
+# interleave "-I" to tell diffutils to 'I'gnore the baselined lines
+baselinedChanges = list(itertools.chain.from_iterable(zip(["-I"]*99, baselinedChanges)))
+
+# post-process the diff output to remove multi-line changes that can't be excluded in `diff` itself
+def filterOutUnskippableBaselinedChanges(inputString):
+ result = inputString
+ for toRemove in unskippableBaselinedChanges:
+ i = result.find(toRemove)
+ while (i != -1):
+ j = result.rfind("\n", 0, i-2) # also find and remove previous line e.g. 82,96c70
+ result = result[:j+1] + result[i+len(toRemove):]
+ i = result.find(toRemove)
+ #remove all "diff -r ..." header lines that no longer have content due to baselining
+ result = result.split("\n")
+ nRemoved = 0
+ for i in range(len(result)): # check for consecutive `diff -r` lines: the first has no content
+ if not result[i-nRemoved].startswith("diff -r "): continue
+ if not result[i+1-nRemoved].startswith("diff -r "): continue
+ del result[i]
+ nRemoved+=1
+ if not result[-1]: del result[-1] # remove possible ending blank line
+ if result[-1].startswith("diff -r "): del result[-1] # terminal `diff -r` line: has no content
+ return "\n".join(result)
+
+# print(baselinedChanges)
+
+# Find all zip files with a diff, e.g. the tip-of-tree-repository file, and maybe the docs zip
+# findFilesMatchingWithDiffAndUnzip(r"**\.[^z][a-z]*")
+# Find all aar and apk files with a diff. The proper regex would be `.*\..*[^akpr]+.*`, but it
+# doesn"t work in difftools exclude's very limited regex syntax.
+findFilesMatchingWithDiffAndUnzip(r"**\.[^a][a-z]*")
+# Find all jars and klibs and unzip them (comes after because they could be inside aars/apks).
+findFilesMatchingWithDiffAndUnzip(r"**\.[^j][a-z]*")
+findFilesMatchingWithDiffAndUnzip(r"**\.[^k][a-z]*")
+# now find all diffs in classes.jars
+classesJarsWithDiffs = popenAndReturn(["find", "../../out-old/dist/", "-name", "classes.jar"])
+print("classes.jar s: " + str(classesJarsWithDiffs))
+compareWithDiffuse(classesJarsWithDiffs)
+# Now find all diffs in non-zipped files
+finalExcludes = excludedHashes + excludedFiles + excludedZips + baselinedChanges
+finalDiff = "\n".join(diff(finalExcludes))
+finalDiff = filterOutUnskippableBaselinedChanges(finalDiff)
+print(finalDiff)
+
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index f0650b2..d1966329 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -223,31 +223,31 @@
docs("androidx.media2:media2-widget:1.3.0")
docs("androidx.media:media:1.7.0")
// androidx.media3 is not hosted in androidx
- docsWithoutApiSince("androidx.media3:media3-cast:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-common:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-container:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-database:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-datasource:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-datasource-cronet:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-datasource-okhttp:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-datasource-rtmp:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-decoder:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-effect:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-exoplayer:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-exoplayer-dash:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-exoplayer-hls:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-exoplayer-ima:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-exoplayer-rtsp:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-exoplayer-smoothstreaming:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-exoplayer-workmanager:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-extractor:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-muxer:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-session:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-test-utils:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-test-utils-robolectric:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-transformer:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-ui:1.4.0")
- docsWithoutApiSince("androidx.media3:media3-ui-leanback:1.4.0")
+ docsWithoutApiSince("androidx.media3:media3-cast:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-common:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-container:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-database:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-datasource:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-datasource-cronet:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-datasource-okhttp:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-datasource-rtmp:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-decoder:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-effect:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-exoplayer:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-exoplayer-dash:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-exoplayer-hls:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-exoplayer-ima:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-exoplayer-rtsp:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-exoplayer-smoothstreaming:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-exoplayer-workmanager:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-extractor:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-muxer:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-session:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-test-utils:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-test-utils-robolectric:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-transformer:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-ui:1.4.1")
+ docsWithoutApiSince("androidx.media3:media3-ui-leanback:1.4.1")
docs("androidx.mediarouter:mediarouter:1.7.0")
docs("androidx.mediarouter:mediarouter-testing:1.7.0")
docs("androidx.metrics:metrics-performance:1.0.0-beta01")
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index e73a6ca..b4d4e4f 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -52,6 +52,8 @@
docs(project(":bluetooth:bluetooth-testing"))
docs(project(":browser:browser"))
docs(project(":camera:camera-camera2"))
+ docs(project(":camera:camera-compose"))
+ samples(project(":camera:camera-compose:camera-compose-samples"))
docs(project(":camera:camera-core"))
docs(project(":camera:camera-effects"))
docs(project(":camera:camera-effects-still-portrait"))
@@ -84,6 +86,7 @@
kmpDocs(project(":compose:material3:adaptive:adaptive"))
kmpDocs(project(":compose:material3:adaptive:adaptive-layout"))
kmpDocs(project(":compose:material3:adaptive:adaptive-navigation"))
+ kmpDocs(project(":compose:material3:adaptive:adaptive-render-strategy"))
kmpDocs(project(":compose:material3:material3"))
kmpDocs(project(":compose:material3:material3-adaptive-navigation-suite"))
kmpDocs(project(":compose:material3:material3-common"))
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml
index 4d42ff9..a35d75c 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml
@@ -18,7 +18,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="emoji_category_recent" msgid="7142376595414250279">"المستخدمة حديثًا"</string>
- <string name="emoji_category_emotions" msgid="1570830970240985537">"الوجوه المبتسمة والرموز التعبيرية"</string>
+ <string name="emoji_category_emotions" msgid="1570830970240985537">"الوجوه المبتسمة ورموز الإيموجي"</string>
<string name="emoji_category_people" msgid="7968173366822927025">"الأشخاص"</string>
<string name="emoji_category_animals_nature" msgid="4640771324837307541">"الحيوانات والطبيعة"</string>
<string name="emoji_category_food_drink" msgid="1189971856721244395">"المأكولات والمشروبات"</string>
@@ -27,11 +27,11 @@
<string name="emoji_category_objects" msgid="6106115586332708067">"عناصر متنوعة"</string>
<string name="emoji_category_symbols" msgid="5626171724310261787">"الرموز"</string>
<string name="emoji_category_flags" msgid="6185639503532784871">"الأعلام"</string>
- <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"لا تتوفر أي رموز تعبيرية."</string>
- <string name="emoji_empty_recent_category" msgid="7863877827879290200">"لم تستخدم أي رموز تعبيرية حتى الآن."</string>
- <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"مفتاح ثنائي الاتجاه للرموز التعبيرية"</string>
+ <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"لا تتوفر أي رموز إيموجي."</string>
+ <string name="emoji_empty_recent_category" msgid="7863877827879290200">"لم تستخدم أي رموز إيموجي حتى الآن."</string>
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"مفتاح ثنائي الاتجاه لرموز الإيموجي"</string>
<string name="emoji_bidirectional_switcher_clicked_desc" msgid="5055290162204827523">"تم تغيير اتجاه الإيموجي"</string>
- <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"أداة اختيار الرموز التعبيرية"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"أداة اختيار رموز الإيموجي"</string>
<string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s و%2$s"</string>
<string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"الظل"</string>
<string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"بشرة فاتحة"</string>
diff --git a/fragment/fragment-compose/src/androidTest/java/androidx/fragment/compose/AndroidFragmentTest.kt b/fragment/fragment-compose/src/androidTest/java/androidx/fragment/compose/AndroidFragmentTest.kt
index ac229eb..a1369c7 100644
--- a/fragment/fragment-compose/src/androidTest/java/androidx/fragment/compose/AndroidFragmentTest.kt
+++ b/fragment/fragment-compose/src/androidTest/java/androidx/fragment/compose/AndroidFragmentTest.kt
@@ -33,12 +33,14 @@
import androidx.fragment.app.Fragment
import androidx.fragment.compose.test.EmptyTestActivity
import androidx.fragment.compose.test.R
+import androidx.lifecycle.Lifecycle
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -98,6 +100,68 @@
}
@Test
+ fun addAfterStateSaved() {
+ lateinit var number: MutableState<Int>
+ testRule.setContent {
+ number = remember { mutableStateOf(0) }
+ if (number.value > 0) {
+ AndroidFragment<FragmentForCompose>()
+ }
+ }
+
+ testRule.activityRule.scenario.moveToState(Lifecycle.State.CREATED)
+
+ testRule.runOnIdle { number.value = 1 }
+
+ testRule.waitForIdle()
+
+ testRule.activityRule.scenario.moveToState(Lifecycle.State.RESUMED)
+
+ onView(withText("Show me on Screen")).check(matches(isDisplayed()))
+
+ testRule.runOnIdle { number.value = 0 }
+
+ testRule.waitForIdle()
+
+ // Validate that the fragment was removed
+ val fragment =
+ testRule.activity.supportFragmentManager.fragments.firstOrNull {
+ it is FragmentForCompose
+ }
+ assertThat(fragment).isNull()
+ }
+
+ @Test
+ fun addAndRemoveAfterStateSaved() {
+ lateinit var number: MutableState<Int>
+ testRule.setContent {
+ number = remember { mutableStateOf(0) }
+ if (number.value > 0) {
+ AndroidFragment<FragmentForCompose>()
+ }
+ }
+
+ testRule.activityRule.scenario.moveToState(Lifecycle.State.CREATED)
+
+ testRule.runOnIdle { number.value = 1 }
+
+ testRule.waitForIdle()
+
+ testRule.runOnIdle { number.value = 0 }
+
+ testRule.waitForIdle()
+
+ testRule.activityRule.scenario.moveToState(Lifecycle.State.RESUMED)
+
+ // Validate that the fragment was removed
+ val fragment =
+ testRule.activity.supportFragmentManager.fragments.firstOrNull {
+ it is FragmentForCompose
+ }
+ assertThat(fragment).isNull()
+ }
+
+ @Test
fun recomposeInsideKey() {
lateinit var number: MutableState<Int>
diff --git a/fragment/fragment-compose/src/main/java/androidx/fragment/compose/AndroidFragment.kt b/fragment/fragment-compose/src/main/java/androidx/fragment/compose/AndroidFragment.kt
index 5d9884b..7d455e9 100644
--- a/fragment/fragment-compose/src/main/java/androidx/fragment/compose/AndroidFragment.kt
+++ b/fragment/fragment-compose/src/main/java/androidx/fragment/compose/AndroidFragment.kt
@@ -30,6 +30,8 @@
import androidx.fragment.app.FragmentContainerView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.commitNow
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
/**
* Allows for adding a [Fragment] directly into Compose. It creates a fragment of the given class
@@ -93,6 +95,7 @@
)
DisposableEffect(fragmentManager, clazz, fragmentState) {
+ var removeEvenIfStateIsSaved = false
val fragment =
fragmentManager.findFragmentById(container.id)
?: fragmentManager.fragmentFactory
@@ -100,18 +103,42 @@
.apply {
setInitialSavedState(fragmentState.state.value)
setArguments(arguments)
- fragmentManager
- .beginTransaction()
- .setReorderingAllowed(true)
- .add(container, this, "$hashKey")
- .commitNow()
+ val transaction =
+ fragmentManager
+ .beginTransaction()
+ .setReorderingAllowed(true)
+ .add(container, this, "$hashKey")
+ if (fragmentManager.isStateSaved) {
+ // If the state is saved when we add the fragment,
+ // we want to remove the Fragment in onDispose
+ // if isStateSaved never becomes true for the lifetime
+ // of this AndroidFragment - we use a LifecycleObserver
+ // on the Fragment as a proxy for that signal
+ removeEvenIfStateIsSaved = true
+ lifecycle.addObserver(
+ object : DefaultLifecycleObserver {
+ override fun onStart(owner: LifecycleOwner) {
+ removeEvenIfStateIsSaved = false
+ lifecycle.removeObserver(this)
+ }
+ }
+ )
+ transaction.commitNowAllowingStateLoss()
+ } else {
+ transaction.commitNow()
+ }
}
fragmentManager.onContainerAvailable(container)
@Suppress("UNCHECKED_CAST") updateCallback.value(fragment as T)
onDispose {
val state = fragmentManager.saveFragmentInstanceState(fragment)
fragmentState.state.value = state
- if (!fragmentManager.isStateSaved) {
+ if (removeEvenIfStateIsSaved) {
+ // The Fragment was added when the state was saved and
+ // isStateSaved never became true for the lifetime of this
+ // AndroidFragment, so we unconditionally remove it here
+ fragmentManager.commitNow(allowStateLoss = true) { remove(fragment) }
+ } else if (!fragmentManager.isStateSaved) {
// If the state isn't saved, that means that some state change
// has removed this Composable from the hierarchy
fragmentManager.commitNow { remove(fragment) }
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PredictiveBackTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PredictiveBackTest.kt
index f9ef07b..340f50b 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PredictiveBackTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PredictiveBackTest.kt
@@ -114,4 +114,30 @@
assertThat(fm.backStackEntryCount).isEqualTo(0)
}
}
+
+ @Test
+ fun backOnNoRecordTest() {
+ withUse(ActivityScenario.launch(SimpleContainerActivity::class.java)) {
+ val fm = withActivity { supportFragmentManager }
+
+ val fragment1 = StrictViewFragment()
+
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment1, "1")
+ .setReorderingAllowed(true)
+ .commit()
+ executePendingTransactions()
+
+ val dispatcher = withActivity { onBackPressedDispatcher }
+
+ // We need a pending commit that doesn't include a fragment to mimic calling
+ // system back while commit is pending.
+ fm.beginTransaction().commit()
+
+ dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+ withActivity { dispatcher.onBackPressed() }
+
+ assertThat(fm.backStackEntryCount).isEqualTo(0)
+ }
+ }
}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index e3d35ae..15ce216 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -2595,6 +2595,16 @@
boolean prepareBackStackState(@NonNull ArrayList<BackStackRecord> records,
@NonNull ArrayList<Boolean> isRecordPop) {
+ if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+ Log.v(
+ TAG, "FragmentManager has the following pending actions inside of "
+ + "prepareBackStackState: " + mPendingActions
+ );
+ }
+ if (mBackStack.isEmpty()) {
+ Log.i(TAG, "Ignoring call to start back stack pop because the back stack is empty.");
+ return false;
+ }
// The transitioning record is the last one on the back stack.
mTransitioningOp = mBackStack.get(mBackStack.size() - 1);
// Mark all fragments in the record as transitioning
diff --git a/glance/glance-appwidget/src/main/res/values-fa/strings.xml b/glance/glance-appwidget/src/main/res/values-fa/strings.xml
index 5dd49dc..313a5b7 100644
--- a/glance/glance-appwidget/src/main/res/values-fa/strings.xml
+++ b/glance/glance-appwidget/src/main/res/values-fa/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="glance_error_layout_title" msgid="3631961919234443531">""<b>"خطا در ابزارک برنامه Glance"</b></string>
+ <string name="glance_error_layout_title" msgid="3631961919234443531">""<b>"خطا در ابزاره برنامه Glance"</b></string>
<string name="glance_error_layout_text" msgid="2863935784364843033">"بااستفاده از "<b><tt>"adb logcat"</tt></b>" و جستجوی "<b><tt>"GlanceAppWidget"</tt></b>"، خطا را بهطور دقیق بررسی کنید"</string>
<string name="glance_error_layout_text_v2" msgid="5191168365305634625">"محتوا نشان داده نشد"</string>
</resources>
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 90e4eb5af..5165817 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -57,7 +57,7 @@
ktfmt = "0.50"
leakcanary = "2.13"
media3 = "1.1.0"
-metalava = "1.0.0-alpha11"
+metalava = "1.0.0-alpha12"
mockito = "2.25.0"
moshi = "1.13.0"
node = "16.20.2"
@@ -143,7 +143,7 @@
espressoIntents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso" }
espressoRemote = { module = "androidx.test.espresso:espresso-remote", version.ref = "espresso" }
espressoWeb = { module = "androidx.test.espresso:espresso-web", version.ref = "espresso" }
-errorProne = { module = "com.google.errorprone:error_prone_core", version = "2.23.0" }
+errorProne = { module = "com.google.errorprone:error_prone_core", version = "2.30.0" }
findbugs = { module = "com.google.code.findbugs:jsr305", version = "3.0.2" }
firebaseAppindexing = { module = "com.google.firebase:firebase-appindexing", version = "19.2.0" }
freemarker = { module = "org.freemarker:freemarker", version = "2.3.31"}
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index d43dc18..2d28c739 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -175,6 +175,7 @@
<trusting group="com.google.testing.compile"/>
<trusting group="com.google.truth"/>
<trusting group="com.google.truth.extensions"/>
+ <trusting group="org.jspecify"/>
</trusted-key>
<trusted-key id="44FBDBBC1A00FE414F1C1873586654072EAD6677" group="org.sonatype.oss"/>
<trusted-key id="47504B76CF89C15C0512D9AFE16AB52D79FD224F">
@@ -264,6 +265,9 @@
<trusting group="org.jetbrains.kotlin"/>
<trusting group="org.jetbrains.kotlin.jvm"/>
<trusting group="org.jetbrains.kotlin.plugin.serialization"/>
+ <trusting group="" name="kotlin-native-prebuilt-linux-x86_64"/>
+ <trusting group="" name="kotlin-native-prebuilt-macos-aarch64"/>
+ <trusting group="" name="kotlin-native-prebuilt-macos-x86_64"/>
</trusted-key>
<trusted-key id="6F656B7F6BFB238D38ACF81F3C27D97B0C83A85C" group="com.google.errorprone"/>
<trusted-key id="6F7E5ACBCD02DB60DFD232E45E1F79A7C298661E">
@@ -552,21 +556,6 @@
</trusted-keys>
</configuration>
<components>
- <component group="" name="kotlin-native-prebuilt-linux-x86_64" version="2.0.10">
- <artifact name="kotlin-native-prebuilt-linux-x86_64-2.0.10.tar.gz">
- <sha256 value="c48badc9e0a01ed084bad6b13d81315910e27d50178351445c3c81991b6f090a" origin="Hand-built using sha256sum kotlin-native-prebuilt-linux-x86_64-2.0.0.tar.gz" reason="https://youtrack.jetbrains.com/issue/KT-52483"/>
- </artifact>
- </component>
- <component group="" name="kotlin-native-prebuilt-macos-aarch64" version="2.0.10">
- <artifact name="kotlin-native-prebuilt-macos-aarch64-2.0.10.tar.gz">
- <sha256 value="3a8f31ab752acf52324299d945df9d32ad65043411701e0be47b51fef75decbb" origin="Hand-built using sha256sum kotlin-native-prebuilt-macos-aarch64-2.0.0.tar.gz" reason="https://youtrack.jetbrains.com/issue/KT-52483"/>
- </artifact>
- </component>
- <component group="" name="kotlin-native-prebuilt-macos-x86_64" version="2.0.10">
- <artifact name="kotlin-native-prebuilt-macos-x86_64-2.0.10.tar.gz">
- <sha256 value="3162f53b70aeaaa4a8d47217ea5edad21b2b4ccb9e30663d5462a04df40ad9ef" origin="Hand-built using sha256sum kotlin-native-prebuilt-macos-x86_64-2.0.0.tar.gz" reason="https://youtrack.jetbrains.com/issue/KT-52483"/>
- </artifact>
- </component>
<component group="androidx.compose.compiler" name="compiler" version="1.5.14">
<artifact name="compiler-1.5.14.jar">
<sha256 value="1ed55641e81177b693aa01f832bb1fd80bfb354caa8a9307c390f7a6738c803c" origin="Generated by Gradle" reason="Artifact is not signed"/>
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/LowLatencyCanvasViewTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/LowLatencyCanvasViewTest.kt
index f046ada..299c232 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/LowLatencyCanvasViewTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/LowLatencyCanvasViewTest.kt
@@ -114,14 +114,24 @@
scenarioCallback = { scenario ->
val resumeLatch = CountDownLatch(1)
val drawLatch = CountDownLatch(1)
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
+ var lowLatencyView: LowLatencyCanvasView? = null
+ scenario.onActivity {
val view = it.getLowLatencyCanvasView()
- view.clear()
view.post { drawLatch.countDown() }
resumeLatch.countDown()
+ lowLatencyView = view
}
assertTrue(resumeLatch.await(3000, TimeUnit.MILLISECONDS))
assertTrue(drawLatch.await(3000, TimeUnit.MILLISECONDS))
+
+ val clearLatch = CountDownLatch(1)
+ scenario.onActivity {
+ lowLatencyView?.let {
+ it.clear()
+ it.post { clearLatch.countDown() }
+ }
+ }
+ assertTrue(clearLatch.await(3000, TimeUnit.MILLISECONDS))
},
validateBitmap = { bitmap, left, top, right, bottom ->
Color.WHITE == bitmap.getPixel(left + (right - left) / 2, top + (bottom - top) / 2)
@@ -197,9 +207,7 @@
}
},
scenarioCallback = { scenario ->
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- it.getLowLatencyCanvasView().renderFrontBufferedLayer()
- }
+ scenario.onActivity { it.getLowLatencyCanvasView().renderFrontBufferedLayer() }
assertTrue(renderFrontBufferLatch.await(3000, TimeUnit.MILLISECONDS))
},
validateBitmap = { bitmap, left, top, right, bottom ->
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt
index 966c457..b417a45 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt
@@ -49,36 +49,32 @@
var surfaceView: SurfaceView? = null
val destroyLatch = CountDownLatch(1)
val scenario =
- ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(Lifecycle.State.CREATED)
- .onActivity {
- it.setDestroyCallback { destroyLatch.countDown() }
- val callback =
- object : SurfaceHolder.Callback {
- override fun surfaceCreated(sh: SurfaceHolder) {
- surfaceView = it.mSurfaceView
- onSurfaceCreated(surfaceView!!, setupLatch)
- }
-
- override fun surfaceChanged(
- holder: SurfaceHolder,
- format: Int,
- width: Int,
- height: Int
- ) {
- // NO-OP
- }
-
- override fun surfaceDestroyed(holder: SurfaceHolder) {
- // NO-OP
- }
+ ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java).onActivity {
+ it.setDestroyCallback { destroyLatch.countDown() }
+ val callback =
+ object : SurfaceHolder.Callback {
+ override fun surfaceCreated(sh: SurfaceHolder) {
+ surfaceView = it.mSurfaceView
+ onSurfaceCreated(surfaceView!!, setupLatch)
}
- it.addSurface(it.mSurfaceView, callback)
- surfaceView = it.mSurfaceView
- }
+ override fun surfaceChanged(
+ holder: SurfaceHolder,
+ format: Int,
+ width: Int,
+ height: Int
+ ) {
+ // NO-OP
+ }
- scenario.moveToState(Lifecycle.State.RESUMED)
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ // NO-OP
+ }
+ }
+
+ it.addSurface(it.mSurfaceView, callback)
+ surfaceView = it.mSurfaceView
+ }
Assert.assertTrue(setupLatch.await(3000, TimeUnit.MILLISECONDS))
val coords = intArrayOf(0, 0)
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
index 4a3e5b9..dec948e 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
@@ -137,9 +137,7 @@
fun testSurfaceTransactionOnCompleteCallback() {
val listener = TransactionOnCompleteListener()
- val scenario =
- ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(Lifecycle.State.CREATED)
+ val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
val destroyLatch = CountDownLatch(1)
try {
@@ -150,8 +148,6 @@
.commit()
}
- scenario.moveToState(Lifecycle.State.RESUMED)
-
listener.mLatch.await(3, TimeUnit.SECONDS)
assertEquals(0, listener.mLatch.count)
assertTrue(listener.mCallbackTime > 0)
@@ -167,9 +163,7 @@
fun testSurfaceTransactionOnCommitCallback() {
val listener = TransactionOnCommitListener()
- val scenario =
- ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(Lifecycle.State.CREATED)
+ val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
val destroyLatch = CountDownLatch(1)
try {
@@ -179,7 +173,6 @@
.addTransactionCommittedListener(executor!!, listener)
.commit()
}
- scenario.moveToState(Lifecycle.State.RESUMED)
listener.mLatch.await(3, TimeUnit.SECONDS)
assertEquals(0, listener.mLatch.count)
@@ -197,9 +190,7 @@
val listener = TransactionOnCommitListener()
val listener2 = TransactionOnCommitListener()
- val scenario =
- ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(Lifecycle.State.CREATED)
+ val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
val destroyLatch = CountDownLatch(1)
try {
@@ -211,8 +202,6 @@
.commit()
}
- scenario.moveToState(Lifecycle.State.RESUMED)
-
listener.mLatch.await(3, TimeUnit.SECONDS)
listener2.mLatch.await(3, TimeUnit.SECONDS)
@@ -234,9 +223,7 @@
val listener1 = TransactionOnCommitListener()
val listener2 = TransactionOnCompleteListener()
- val scenario =
- ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(Lifecycle.State.CREATED)
+ val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
val destroyLatch = CountDownLatch(1)
try {
@@ -248,8 +235,6 @@
.commit()
}
- scenario.moveToState(Lifecycle.State.RESUMED)
-
listener1.mLatch.await(3, TimeUnit.SECONDS)
listener2.mLatch.await(3, TimeUnit.SECONDS)
@@ -852,39 +837,37 @@
var scCompat: SurfaceControlWrapper? = null
val listener = TransactionOnCompleteListener()
val scenario =
- ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(Lifecycle.State.CREATED)
- .onActivity {
- val callback =
- object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- scCompat =
- SurfaceControlWrapper.Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlCompatTest")
- .build()
+ ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java).onActivity {
+ val callback =
+ object : SurfaceHolderCallback() {
+ override fun surfaceCreated(sh: SurfaceHolder) {
+ scCompat =
+ SurfaceControlWrapper.Builder()
+ .setParent(it.getSurfaceView().holder.surface)
+ .setDebugName("SurfaceControlCompatTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer =
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer =
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat!!, buffer)
- .setVisibility(scCompat!!, true)
- .setCrop(scCompat!!, Rect(20, 30, 90, 60))
- .commit()
- }
+ SurfaceControlWrapper.Transaction()
+ .addTransactionCompletedListener(listener)
+ .setBuffer(scCompat!!, buffer)
+ .setVisibility(scCompat!!, true)
+ .setCrop(scCompat!!, Rect(20, 30, 90, 60))
+ .commit()
}
+ }
- it.addSurface(it.mSurfaceView, callback)
- }
+ it.addSurface(it.mSurfaceView, callback)
+ }
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
+ scenario.onActivity {
assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
SurfaceControlUtils.validateOutput { bitmap ->
val coord = intArrayOf(0, 0)
diff --git a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/PolygonTest.kt b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/PolygonTest.kt
index fd9bb84..c7f7c75 100644
--- a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/PolygonTest.kt
+++ b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/PolygonTest.kt
@@ -16,6 +16,7 @@
package androidx.graphics.shapes
+import android.graphics.Matrix
import androidx.test.filters.SmallTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
@@ -200,6 +201,57 @@
assertNotEquals(preTransformCenters, postTransformCenters)
}
+ @Test
+ fun transformKeepsContiguousAnchorsEqual() {
+ val poly =
+ RoundedPolygon(radius = 1f, numVertices = 4, rounding = CornerRounding(7 / 15f))
+ .transformed(
+ Matrix().apply {
+ postRotate(45f)
+ postScale(648f, 648f)
+ postTranslate(540f, 1212f)
+ }
+ )
+ poly.cubics.indices.forEach { i ->
+ // It has to be the same point
+ assertEquals(
+ "Failed at X, index $i",
+ poly.cubics[i].anchor1X,
+ poly.cubics[(i + 1) % poly.cubics.size].anchor0X,
+ 0f
+ )
+ assertEquals(
+ "Failed at Y, index $i",
+ poly.cubics[i].anchor1Y,
+ poly.cubics[(i + 1) % poly.cubics.size].anchor0Y,
+ 0f
+ )
+ }
+ }
+
+ @Test
+ fun emptyPolygonTest() {
+ val poly = RoundedPolygon(6, radius = 0f, rounding = CornerRounding(0.1f))
+ assert(poly.cubics.size == 1)
+
+ val stillEmpty = poly.transformed(scaleTransform(10f, 20f))
+ assert(stillEmpty.cubics.size == 1)
+ assert(stillEmpty.cubics.first().zeroLength())
+ }
+
+ @Test
+ fun emptySideTest() {
+ val poly1 =
+ RoundedPolygon(
+ floatArrayOf(0f, 0f, 1f, 0f, 1f, 0f, 0f, 1f), // Triangle with one point repeated
+ )
+ val poly2 =
+ RoundedPolygon(
+ floatArrayOf(0f, 0f, 1f, 0f, 0f, 1f), // Triangle
+ )
+ assertCubicListsEqualish(poly1.cubics, poly2.cubics)
+ }
+
private fun nonzeroCubics(original: List<Cubic>): List<Cubic> {
val result = mutableListOf<Cubic>()
for (i in original.indices) {
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt
index b2fc8f2..5ada39e 100644
--- a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt
@@ -67,13 +67,14 @@
// enough discontinuity to throw an exception later, even though the
// distances are quite small. Account for that by making the last
// cubic use the latest anchor point, always.
+ lastCubic = Cubic(lastCubic.points.copyOf()) // Make a copy before mutating
lastCubic.points[6] = cubic.anchor1X
lastCubic.points[7] = cubic.anchor1Y
}
}
}
}
- if (lastCubic != null && firstCubic != null)
+ if (lastCubic != null && firstCubic != null) {
add(
Cubic(
lastCubic.anchor0X,
@@ -86,6 +87,21 @@
firstCubic.anchor0Y
)
)
+ } else {
+ // Empty / 0-sized polygon.
+ add(
+ Cubic(
+ centerX,
+ centerY,
+ centerX,
+ centerY,
+ centerX,
+ centerY,
+ centerX,
+ centerY,
+ )
+ )
+ }
}
init {
@@ -99,12 +115,11 @@
abs(cubic.anchor0Y - prevCubic.anchor1Y) > DistanceEpsilon
) {
debugLog("RoundedPolygon") {
- "Ix: $index | (${cubic.anchor0X},${cubic.anchor0Y}) vs " + "$prevCubic"
+ "Ix: $index | (${cubic.anchor0X},${cubic.anchor0Y}) vs $prevCubic"
}
throw IllegalArgumentException(
- "RoundedPolygon must be contiguous, with the " +
- "anchor points of all curves matching the anchor points of the preceding " +
- "and succeeding cubics"
+ "RoundedPolygon must be contiguous, with the anchor points of all curves " +
+ "matching the anchor points of the preceding and succeeding cubics"
)
}
prevCubic = cubic
@@ -482,27 +497,51 @@
val p2: Point,
val rounding: CornerRounding? = null
) {
- val d1 = (p0 - p1).getDirection()
- val d2 = (p2 - p1).getDirection()
- val cornerRadius = rounding?.radius ?: 0f
- val smoothing = rounding?.smoothing ?: 0f
+ val d1: Point
+ val d2: Point
+ val cornerRadius: Float
+ val smoothing: Float
+ val cosAngle: Float
+ val sinAngle: Float
+ val expectedRoundCut: Float
- // cosine of angle at p1 is dot product of unit vectors to the other two vertices
- val cosAngle = d1.dotProduct(d2)
+ init {
+ val v01 = p0 - p1
+ val v21 = p2 - p1
+ val d01 = v01.getDistance()
+ val d21 = v21.getDistance()
+ if (d01 > 0f && d21 > 0f) {
+ d1 = v01 / d01
+ d2 = v21 / d21
+ cornerRadius = rounding?.radius ?: 0f
+ smoothing = rounding?.smoothing ?: 0f
- // identity: sin^2 + cos^2 = 1
- // sinAngle gives us the intersection
- val sinAngle = sqrt(1 - square(cosAngle))
+ // cosine of angle at p1 is dot product of unit vectors to the other two vertices
+ cosAngle = d1.dotProduct(d2)
- // How much we need to cut, as measured on a side, to get the required radius
- // calculating where the rounding circle hits the edge
- // This uses the identity of tan(A/2) = sinA/(1 + cosA), where tan(A/2) = radius/cut
- val expectedRoundCut =
- if (sinAngle > 1e-3) {
- cornerRadius * (cosAngle + 1) / sinAngle
+ // identity: sin^2 + cos^2 = 1
+ // sinAngle gives us the intersection
+ sinAngle = sqrt(1 - square(cosAngle))
+ // How much we need to cut, as measured on a side, to get the required radius
+ // calculating where the rounding circle hits the edge
+ // This uses the identity of tan(A/2) = sinA/(1 + cosA), where tan(A/2) = radius/cut
+ expectedRoundCut =
+ if (sinAngle > 1e-3) {
+ cornerRadius * (cosAngle + 1) / sinAngle
+ } else {
+ 0f
+ }
} else {
- 0f
+ // One (or both) of the sides is empty, not much we can do.
+ d1 = Point(0f, 0f)
+ d2 = Point(0f, 0f)
+ cornerRadius = 0f
+ smoothing = 0f
+ cosAngle = 0f
+ sinAngle = 0f
+ expectedRoundCut = 0f
}
+ }
// smoothing changes the actual cut. 0 is same as expectedRoundCut, 1 doubles it
val expectedCut: Float
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index 9fd7f1f..d5d6059 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -1133,42 +1133,6 @@
public static final class SexualActivityRecord.Companion {
}
- public final class SkinTemperatureRecord implements androidx.health.connect.client.records.Record {
- ctor public SkinTemperatureRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List<androidx.health.connect.client.records.SkinTemperatureRecord.Delta> deltas, optional androidx.health.connect.client.units.Temperature? baseline, optional int measurementLocation, optional androidx.health.connect.client.records.metadata.Metadata metadata);
- method public androidx.health.connect.client.units.Temperature? getBaseline();
- method public java.util.List<androidx.health.connect.client.records.SkinTemperatureRecord.Delta> getDeltas();
- method public java.time.Instant getEndTime();
- method public java.time.ZoneOffset? getEndZoneOffset();
- method public int getMeasurementLocation();
- method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
- method public java.time.Instant getStartTime();
- method public java.time.ZoneOffset? getStartZoneOffset();
- property public final androidx.health.connect.client.units.Temperature? baseline;
- property public final java.util.List<androidx.health.connect.client.records.SkinTemperatureRecord.Delta> deltas;
- property public java.time.Instant endTime;
- property public java.time.ZoneOffset? endZoneOffset;
- property public final int measurementLocation;
- property public androidx.health.connect.client.records.metadata.Metadata metadata;
- property public java.time.Instant startTime;
- property public java.time.ZoneOffset? startZoneOffset;
- field public static final androidx.health.connect.client.records.SkinTemperatureRecord.Companion Companion;
- field public static final int MEASUREMENT_LOCATION_FINGER = 1; // 0x1
- field public static final int MEASUREMENT_LOCATION_TOE = 2; // 0x2
- field public static final int MEASUREMENT_LOCATION_UNKNOWN = 0; // 0x0
- field public static final int MEASUREMENT_LOCATION_WRIST = 3; // 0x3
- }
-
- public static final class SkinTemperatureRecord.Companion {
- }
-
- public static final class SkinTemperatureRecord.Delta {
- ctor public SkinTemperatureRecord.Delta(java.time.Instant time, androidx.health.connect.client.units.TemperatureDelta delta);
- method public androidx.health.connect.client.units.TemperatureDelta getDelta();
- method public java.time.Instant getTime();
- property public final androidx.health.connect.client.units.TemperatureDelta delta;
- property public final java.time.Instant time;
- }
-
public final class SleepSessionRecord implements androidx.health.connect.client.records.Record {
ctor public SleepSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional String? title, optional String? notes, optional java.util.List<androidx.health.connect.client.records.SleepSessionRecord.Stage> stages, optional androidx.health.connect.client.records.metadata.Metadata metadata);
method public java.time.Instant getEndTime();
@@ -1680,22 +1644,6 @@
method public androidx.health.connect.client.units.Temperature fahrenheit(double value);
}
- public final class TemperatureDelta implements java.lang.Comparable<androidx.health.connect.client.units.TemperatureDelta> {
- method public static androidx.health.connect.client.units.TemperatureDelta celsius(double value);
- method public int compareTo(androidx.health.connect.client.units.TemperatureDelta other);
- method public static androidx.health.connect.client.units.TemperatureDelta fahrenheit(double value);
- method public double getCelsius();
- method public double getFahrenheit();
- property public final double inCelsius;
- property public final double inFahrenheit;
- field public static final androidx.health.connect.client.units.TemperatureDelta.Companion Companion;
- }
-
- public static final class TemperatureDelta.Companion {
- method public androidx.health.connect.client.units.TemperatureDelta celsius(double value);
- method public androidx.health.connect.client.units.TemperatureDelta fahrenheit(double value);
- }
-
public final class Velocity implements java.lang.Comparable<androidx.health.connect.client.units.Velocity> {
method public int compareTo(androidx.health.connect.client.units.Velocity other);
method public double getKilometersPerHour();
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index eecd9a63..518453c 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -1156,42 +1156,6 @@
public static final class SexualActivityRecord.Companion {
}
- public final class SkinTemperatureRecord implements androidx.health.connect.client.records.IntervalRecord {
- ctor public SkinTemperatureRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List<androidx.health.connect.client.records.SkinTemperatureRecord.Delta> deltas, optional androidx.health.connect.client.units.Temperature? baseline, optional int measurementLocation, optional androidx.health.connect.client.records.metadata.Metadata metadata);
- method public androidx.health.connect.client.units.Temperature? getBaseline();
- method public java.util.List<androidx.health.connect.client.records.SkinTemperatureRecord.Delta> getDeltas();
- method public java.time.Instant getEndTime();
- method public java.time.ZoneOffset? getEndZoneOffset();
- method public int getMeasurementLocation();
- method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
- method public java.time.Instant getStartTime();
- method public java.time.ZoneOffset? getStartZoneOffset();
- property public final androidx.health.connect.client.units.Temperature? baseline;
- property public final java.util.List<androidx.health.connect.client.records.SkinTemperatureRecord.Delta> deltas;
- property public java.time.Instant endTime;
- property public java.time.ZoneOffset? endZoneOffset;
- property public final int measurementLocation;
- property public androidx.health.connect.client.records.metadata.Metadata metadata;
- property public java.time.Instant startTime;
- property public java.time.ZoneOffset? startZoneOffset;
- field public static final androidx.health.connect.client.records.SkinTemperatureRecord.Companion Companion;
- field public static final int MEASUREMENT_LOCATION_FINGER = 1; // 0x1
- field public static final int MEASUREMENT_LOCATION_TOE = 2; // 0x2
- field public static final int MEASUREMENT_LOCATION_UNKNOWN = 0; // 0x0
- field public static final int MEASUREMENT_LOCATION_WRIST = 3; // 0x3
- }
-
- public static final class SkinTemperatureRecord.Companion {
- }
-
- public static final class SkinTemperatureRecord.Delta {
- ctor public SkinTemperatureRecord.Delta(java.time.Instant time, androidx.health.connect.client.units.TemperatureDelta delta);
- method public androidx.health.connect.client.units.TemperatureDelta getDelta();
- method public java.time.Instant getTime();
- property public final androidx.health.connect.client.units.TemperatureDelta delta;
- property public final java.time.Instant time;
- }
-
public final class SleepSessionRecord implements androidx.health.connect.client.records.IntervalRecord {
ctor public SleepSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional String? title, optional String? notes, optional java.util.List<androidx.health.connect.client.records.SleepSessionRecord.Stage> stages, optional androidx.health.connect.client.records.metadata.Metadata metadata);
method public java.time.Instant getEndTime();
@@ -1703,22 +1667,6 @@
method public androidx.health.connect.client.units.Temperature fahrenheit(double value);
}
- public final class TemperatureDelta implements java.lang.Comparable<androidx.health.connect.client.units.TemperatureDelta> {
- method public static androidx.health.connect.client.units.TemperatureDelta celsius(double value);
- method public int compareTo(androidx.health.connect.client.units.TemperatureDelta other);
- method public static androidx.health.connect.client.units.TemperatureDelta fahrenheit(double value);
- method public double getCelsius();
- method public double getFahrenheit();
- property public final double inCelsius;
- property public final double inFahrenheit;
- field public static final androidx.health.connect.client.units.TemperatureDelta.Companion Companion;
- }
-
- public static final class TemperatureDelta.Companion {
- method public androidx.health.connect.client.units.TemperatureDelta celsius(double value);
- method public androidx.health.connect.client.units.TemperatureDelta fahrenheit(double value);
- }
-
public final class Velocity implements java.lang.Comparable<androidx.health.connect.client.units.Velocity> {
method public int compareTo(androidx.health.connect.client.units.Velocity other);
method public double getKilometersPerHour();
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
index ac23114..14f303c 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
@@ -27,6 +27,7 @@
import androidx.health.connect.client.changes.UpsertionChange
import androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi
import androidx.health.connect.client.impl.converters.datatype.RECORDS_CLASS_NAME_MAP
+import androidx.health.connect.client.impl.platform.aggregate.AGGREGATE_METRICS_ADDED_IN_SDK_EXT_10
import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_PREFIX
import androidx.health.connect.client.readRecord
import androidx.health.connect.client.records.BloodPressureRecord
@@ -59,8 +60,10 @@
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.temporal.ChronoUnit
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.After
+import org.junit.Assert.assertThrows
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Before
@@ -420,6 +423,45 @@
}
}
+ // TODO(b/361297592): Remove once the aggregation bug is fixed
+ @Test
+ fun aggregateRecords_unsupportedMetrics_throwsUOE() = runTest {
+ for (metric in AGGREGATE_METRICS_ADDED_IN_SDK_EXT_10) {
+ assertThrows(UnsupportedOperationException::class.java) {
+ runBlocking {
+ healthConnectClient.aggregate(
+ AggregateRequest(setOf(metric), TimeRangeFilter.none())
+ )
+ }
+ }
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ runBlocking {
+ healthConnectClient.aggregateGroupByDuration(
+ AggregateGroupByDurationRequest(
+ setOf(metric),
+ TimeRangeFilter.none(),
+ Duration.ofDays(1)
+ )
+ )
+ }
+ }
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ runBlocking {
+ healthConnectClient.aggregateGroupByPeriod(
+ AggregateGroupByPeriodRequest(
+ setOf(metric),
+ TimeRangeFilter.none(),
+ Period.ofDays(1)
+ )
+ )
+ }
+ }
+ }
+ }
+
+ @Ignore("b/326414908")
@Test
fun aggregateRecords_belowSdkExt10() = runTest {
assumeFalse(SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10)
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
index 13cbe90..e6f24bb 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
@@ -35,6 +35,7 @@
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.HealthConnectFeatures
import androidx.health.connect.client.PermissionController
+import androidx.health.connect.client.aggregate.AggregateMetric
import androidx.health.connect.client.aggregate.AggregationResult
import androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration
import androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod
@@ -42,6 +43,7 @@
import androidx.health.connect.client.changes.UpsertionChange
import androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi
import androidx.health.connect.client.feature.HealthConnectFeaturesPlatformImpl
+import androidx.health.connect.client.impl.platform.aggregate.AGGREGATE_METRICS_ADDED_IN_SDK_EXT_10
import androidx.health.connect.client.impl.platform.aggregate.aggregateFallback
import androidx.health.connect.client.impl.platform.aggregate.platformMetrics
import androidx.health.connect.client.impl.platform.aggregate.plus
@@ -216,9 +218,7 @@
}
override suspend fun aggregate(request: AggregateRequest): AggregationResult {
- if (request.metrics.isEmpty()) {
- throw IllegalArgumentException("Requested record types must not be empty.")
- }
+ verifyAggregationMetrics(request.metrics)
val fallbackResponse = aggregateFallback(request)
@@ -244,6 +244,8 @@
override suspend fun aggregateGroupByDuration(
request: AggregateGroupByDurationRequest
): List<AggregationResultGroupedByDuration> {
+ verifyAggregationMetrics(request.metrics)
+
return wrapPlatformException {
suspendCancellableCoroutine { continuation ->
healthConnectManager.aggregateGroupByDuration(
@@ -260,6 +262,8 @@
override suspend fun aggregateGroupByPeriod(
request: AggregateGroupByPeriodRequest
): List<AggregationResultGroupedByPeriod> {
+ verifyAggregationMetrics(request.metrics)
+
return wrapPlatformException {
suspendCancellableCoroutine { continuation ->
healthConnectManager.aggregateGroupByPeriod(
@@ -299,6 +303,12 @@
}
}
+ private fun verifyAggregationMetrics(metrics: Set<AggregateMetric<*>>) {
+ AGGREGATE_METRICS_ADDED_IN_SDK_EXT_10.intersect(metrics).firstOrNull()?.let {
+ throw UnsupportedOperationException("Unsupported metric type ${it.metricKey}")
+ }
+ }
+
override suspend fun getChangesToken(request: ChangesTokenRequest): String {
return wrapPlatformException {
suspendCancellableCoroutine { continuation ->
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt
index 68b8036..d3b5712b 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt
@@ -214,6 +214,7 @@
internal const val READ_OXYGEN_SATURATION = PERMISSION_PREFIX + "READ_OXYGEN_SATURATION"
internal const val READ_RESPIRATORY_RATE = PERMISSION_PREFIX + "READ_RESPIRATORY_RATE"
internal const val READ_RESTING_HEART_RATE = PERMISSION_PREFIX + "READ_RESTING_HEART_RATE"
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
internal const val READ_SKIN_TEMPERATURE = PERMISSION_PREFIX + "READ_SKIN_TEMPERATURE"
// Write permissions for ACTIVITY.
@@ -272,6 +273,8 @@
internal const val WRITE_OXYGEN_SATURATION = PERMISSION_PREFIX + "WRITE_OXYGEN_SATURATION"
internal const val WRITE_RESPIRATORY_RATE = PERMISSION_PREFIX + "WRITE_RESPIRATORY_RATE"
internal const val WRITE_RESTING_HEART_RATE = PERMISSION_PREFIX + "WRITE_RESTING_HEART_RATE"
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
internal const val WRITE_SKIN_TEMPERATURE = PERMISSION_PREFIX + "WRITE_SKIN_TEMPERATURE"
internal const val READ_PERMISSION_PREFIX = PERMISSION_PREFIX + "READ_"
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/SkinTemperatureRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/SkinTemperatureRecord.kt
index 9be96d0..6a1b829 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/SkinTemperatureRecord.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/SkinTemperatureRecord.kt
@@ -49,6 +49,7 @@
* [SkinTemperatureMeasurementLocation].
* @param metadata set of common metadata associated with the written record.
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY)
class SkinTemperatureRecord(
override val startTime: Instant,
override val startZoneOffset: ZoneOffset?,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/units/TemperatureDelta.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/units/TemperatureDelta.kt
index 0d5f9ef..abf4905 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/units/TemperatureDelta.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/units/TemperatureDelta.kt
@@ -16,11 +16,14 @@
package androidx.health.connect.client.units
+import androidx.annotation.RestrictTo
+
/**
* Represents a unit of TemperatureDelta difference. Supported units:
* - Celsius - see [TemperatureDelta.celsius], [Double.celsius]
* - Fahrenheit - see [TemperatureDelta.fahrenheit], [Double.fahrenheit]
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY)
class TemperatureDelta
private constructor(
private val value: Double,
diff --git a/hilt/hilt-compiler/build.gradle b/hilt/hilt-compiler/build.gradle
index 3213852..339bafd 100644
--- a/hilt/hilt-compiler/build.gradle
+++ b/hilt/hilt-compiler/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import androidx.build.SdkHelperKt
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@@ -68,4 +70,5 @@
mavenVersion = LibraryVersions.HILT
inceptionYear = "2020"
description = "AndroidX Hilt Extension Compiler"
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/hilt/hilt-navigation-compose/build.gradle b/hilt/hilt-navigation-compose/build.gradle
index 0d2dbc8..7a73f47 100644
--- a/hilt/hilt-navigation-compose/build.gradle
+++ b/hilt/hilt-navigation-compose/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -78,4 +80,5 @@
description = "Navigation Compose Hilt Integration"
legacyDisableKotlinStrictApiMode = true
samples(projectOrArtifact(":hilt:hilt-navigation-compose-samples"))
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/hilt/hilt-navigation-compose/samples/build.gradle b/hilt/hilt-navigation-compose/samples/build.gradle
index cb0491f..c59ff49 100644
--- a/hilt/hilt-navigation-compose/samples/build.gradle
+++ b/hilt/hilt-navigation-compose/samples/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import androidx.build.Publish
@@ -44,6 +46,7 @@
mavenVersion = LibraryVersions.HILT_NAVIGATION_COMPOSE
inceptionYear = "2021"
description = "Samples for the Navigation Compose Hilt Integration"
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
android {
diff --git a/hilt/hilt-navigation-fragment/build.gradle b/hilt/hilt-navigation-fragment/build.gradle
index 5dc55cc1..c78ad99 100644
--- a/hilt/hilt-navigation-fragment/build.gradle
+++ b/hilt/hilt-navigation-fragment/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -69,4 +71,5 @@
inceptionYear = "2021"
description = "Android Navigation Fragment Hilt Extension"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/hilt/hilt-navigation/build.gradle b/hilt/hilt-navigation/build.gradle
index 2e4f65b..6ce18e5 100644
--- a/hilt/hilt-navigation/build.gradle
+++ b/hilt/hilt-navigation/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -45,6 +47,7 @@
inceptionYear = "2021"
description = "Android Navigation Hilt Extension"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
android {
diff --git a/ink/ink-brush/api/current.txt b/ink/ink-brush/api/current.txt
index c10c574..7582fcf 100644
--- a/ink/ink-brush/api/current.txt
+++ b/ink/ink-brush/api/current.txt
@@ -2,11 +2,78 @@
package androidx.ink.brush {
public final class Brush {
- ctor public Brush(long color, float size);
- method public long getColor();
+ ctor public Brush(androidx.ink.brush.BrushFamily family, float size, float epsilon);
+ method public static androidx.ink.brush.Brush.Builder builder();
+ method public androidx.ink.brush.Brush copy();
+ method public androidx.ink.brush.Brush copy(optional androidx.ink.brush.BrushFamily family);
+ method public androidx.ink.brush.Brush copy(optional androidx.ink.brush.BrushFamily family, optional float size);
+ method public androidx.ink.brush.Brush copy(optional androidx.ink.brush.BrushFamily family, optional float size, optional float epsilon);
+ method public androidx.ink.brush.Brush copyWithColorIntArgb(@ColorInt int colorIntArgb);
+ method public androidx.ink.brush.Brush copyWithColorIntArgb(@ColorInt int colorIntArgb, optional androidx.ink.brush.BrushFamily family);
+ method public androidx.ink.brush.Brush copyWithColorIntArgb(@ColorInt int colorIntArgb, optional androidx.ink.brush.BrushFamily family, optional float size);
+ method public androidx.ink.brush.Brush copyWithColorIntArgb(@ColorInt int colorIntArgb, optional androidx.ink.brush.BrushFamily family, optional float size, optional float epsilon);
+ method public androidx.ink.brush.Brush copyWithColorLong(@ColorLong long colorLong);
+ method public androidx.ink.brush.Brush copyWithColorLong(@ColorLong long colorLong, optional androidx.ink.brush.BrushFamily family);
+ method public androidx.ink.brush.Brush copyWithColorLong(@ColorLong long colorLong, optional androidx.ink.brush.BrushFamily family, optional float size);
+ method public androidx.ink.brush.Brush copyWithColorLong(@ColorLong long colorLong, optional androidx.ink.brush.BrushFamily family, optional float size, optional float epsilon);
+ method public static androidx.ink.brush.Brush createWithColorIntArgb(androidx.ink.brush.BrushFamily family, @ColorInt int colorIntArgb, float size, float epsilon);
+ method public static androidx.ink.brush.Brush createWithColorLong(androidx.ink.brush.BrushFamily family, @ColorLong long colorLong, float size, float epsilon);
+ method protected void finalize();
+ method @ColorInt public int getColorIntArgb();
+ method @ColorLong public long getColorLong();
+ method public float getEpsilon();
+ method public androidx.ink.brush.BrushFamily getFamily();
method public float getSize();
- property public final long color;
+ method public androidx.ink.brush.Brush.Builder toBuilder();
+ property @ColorInt public final int colorIntArgb;
+ property @ColorLong public final long colorLong;
+ property public final float epsilon;
+ property public final androidx.ink.brush.BrushFamily family;
property public final float size;
+ field public static final androidx.ink.brush.Brush.Companion Companion;
+ }
+
+ public static final class Brush.Builder {
+ ctor public Brush.Builder();
+ method public androidx.ink.brush.Brush build();
+ method public androidx.ink.brush.Brush.Builder setColorIntArgb(@ColorInt int colorIntArgb);
+ method public androidx.ink.brush.Brush.Builder setColorLong(@ColorLong long colorLong);
+ method public androidx.ink.brush.Brush.Builder setEpsilon(@FloatRange(from=0.0, fromInclusive=false, to=kotlin.jvm.internal.DoubleCompanionObject.POSITIVE_INFINITY, toInclusive=false) float epsilon);
+ method public androidx.ink.brush.Brush.Builder setFamily(androidx.ink.brush.BrushFamily family);
+ method public androidx.ink.brush.Brush.Builder setSize(@FloatRange(from=0.0, fromInclusive=false, to=kotlin.jvm.internal.DoubleCompanionObject.POSITIVE_INFINITY, toInclusive=false) float size);
+ }
+
+ public static final class Brush.Companion {
+ method public androidx.ink.brush.Brush.Builder builder();
+ method public androidx.ink.brush.Brush createWithColorIntArgb(androidx.ink.brush.BrushFamily family, @ColorInt int colorIntArgb, float size, float epsilon);
+ method public androidx.ink.brush.Brush createWithColorLong(androidx.ink.brush.BrushFamily family, @ColorLong long colorLong, float size, float epsilon);
+ }
+
+ public final class BrushFamily {
+ method protected void finalize();
+ field public static final androidx.ink.brush.BrushFamily.Companion Companion;
+ }
+
+ public static final class BrushFamily.Companion {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.PROPERTY, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.TYPEALIAS}) public @interface ExperimentalInkCustomBrushApi {
+ }
+
+ public final class StockBrushes {
+ method public static androidx.ink.brush.BrushFamily getHighlighterLatest();
+ method public static androidx.ink.brush.BrushFamily getHighlighterV1();
+ method public static androidx.ink.brush.BrushFamily getMarkerLatest();
+ method public static androidx.ink.brush.BrushFamily getMarkerV1();
+ method public static androidx.ink.brush.BrushFamily getPressurePenLatest();
+ method public static androidx.ink.brush.BrushFamily getPressurePenV1();
+ property public static final androidx.ink.brush.BrushFamily highlighterLatest;
+ property public static final androidx.ink.brush.BrushFamily highlighterV1;
+ property public static final androidx.ink.brush.BrushFamily markerLatest;
+ property public static final androidx.ink.brush.BrushFamily markerV1;
+ property public static final androidx.ink.brush.BrushFamily pressurePenLatest;
+ property public static final androidx.ink.brush.BrushFamily pressurePenV1;
+ field public static final androidx.ink.brush.StockBrushes INSTANCE;
}
}
diff --git a/ink/ink-brush/api/restricted_current.txt b/ink/ink-brush/api/restricted_current.txt
index c10c574..7582fcf 100644
--- a/ink/ink-brush/api/restricted_current.txt
+++ b/ink/ink-brush/api/restricted_current.txt
@@ -2,11 +2,78 @@
package androidx.ink.brush {
public final class Brush {
- ctor public Brush(long color, float size);
- method public long getColor();
+ ctor public Brush(androidx.ink.brush.BrushFamily family, float size, float epsilon);
+ method public static androidx.ink.brush.Brush.Builder builder();
+ method public androidx.ink.brush.Brush copy();
+ method public androidx.ink.brush.Brush copy(optional androidx.ink.brush.BrushFamily family);
+ method public androidx.ink.brush.Brush copy(optional androidx.ink.brush.BrushFamily family, optional float size);
+ method public androidx.ink.brush.Brush copy(optional androidx.ink.brush.BrushFamily family, optional float size, optional float epsilon);
+ method public androidx.ink.brush.Brush copyWithColorIntArgb(@ColorInt int colorIntArgb);
+ method public androidx.ink.brush.Brush copyWithColorIntArgb(@ColorInt int colorIntArgb, optional androidx.ink.brush.BrushFamily family);
+ method public androidx.ink.brush.Brush copyWithColorIntArgb(@ColorInt int colorIntArgb, optional androidx.ink.brush.BrushFamily family, optional float size);
+ method public androidx.ink.brush.Brush copyWithColorIntArgb(@ColorInt int colorIntArgb, optional androidx.ink.brush.BrushFamily family, optional float size, optional float epsilon);
+ method public androidx.ink.brush.Brush copyWithColorLong(@ColorLong long colorLong);
+ method public androidx.ink.brush.Brush copyWithColorLong(@ColorLong long colorLong, optional androidx.ink.brush.BrushFamily family);
+ method public androidx.ink.brush.Brush copyWithColorLong(@ColorLong long colorLong, optional androidx.ink.brush.BrushFamily family, optional float size);
+ method public androidx.ink.brush.Brush copyWithColorLong(@ColorLong long colorLong, optional androidx.ink.brush.BrushFamily family, optional float size, optional float epsilon);
+ method public static androidx.ink.brush.Brush createWithColorIntArgb(androidx.ink.brush.BrushFamily family, @ColorInt int colorIntArgb, float size, float epsilon);
+ method public static androidx.ink.brush.Brush createWithColorLong(androidx.ink.brush.BrushFamily family, @ColorLong long colorLong, float size, float epsilon);
+ method protected void finalize();
+ method @ColorInt public int getColorIntArgb();
+ method @ColorLong public long getColorLong();
+ method public float getEpsilon();
+ method public androidx.ink.brush.BrushFamily getFamily();
method public float getSize();
- property public final long color;
+ method public androidx.ink.brush.Brush.Builder toBuilder();
+ property @ColorInt public final int colorIntArgb;
+ property @ColorLong public final long colorLong;
+ property public final float epsilon;
+ property public final androidx.ink.brush.BrushFamily family;
property public final float size;
+ field public static final androidx.ink.brush.Brush.Companion Companion;
+ }
+
+ public static final class Brush.Builder {
+ ctor public Brush.Builder();
+ method public androidx.ink.brush.Brush build();
+ method public androidx.ink.brush.Brush.Builder setColorIntArgb(@ColorInt int colorIntArgb);
+ method public androidx.ink.brush.Brush.Builder setColorLong(@ColorLong long colorLong);
+ method public androidx.ink.brush.Brush.Builder setEpsilon(@FloatRange(from=0.0, fromInclusive=false, to=kotlin.jvm.internal.DoubleCompanionObject.POSITIVE_INFINITY, toInclusive=false) float epsilon);
+ method public androidx.ink.brush.Brush.Builder setFamily(androidx.ink.brush.BrushFamily family);
+ method public androidx.ink.brush.Brush.Builder setSize(@FloatRange(from=0.0, fromInclusive=false, to=kotlin.jvm.internal.DoubleCompanionObject.POSITIVE_INFINITY, toInclusive=false) float size);
+ }
+
+ public static final class Brush.Companion {
+ method public androidx.ink.brush.Brush.Builder builder();
+ method public androidx.ink.brush.Brush createWithColorIntArgb(androidx.ink.brush.BrushFamily family, @ColorInt int colorIntArgb, float size, float epsilon);
+ method public androidx.ink.brush.Brush createWithColorLong(androidx.ink.brush.BrushFamily family, @ColorLong long colorLong, float size, float epsilon);
+ }
+
+ public final class BrushFamily {
+ method protected void finalize();
+ field public static final androidx.ink.brush.BrushFamily.Companion Companion;
+ }
+
+ public static final class BrushFamily.Companion {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.PROPERTY, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.TYPEALIAS}) public @interface ExperimentalInkCustomBrushApi {
+ }
+
+ public final class StockBrushes {
+ method public static androidx.ink.brush.BrushFamily getHighlighterLatest();
+ method public static androidx.ink.brush.BrushFamily getHighlighterV1();
+ method public static androidx.ink.brush.BrushFamily getMarkerLatest();
+ method public static androidx.ink.brush.BrushFamily getMarkerV1();
+ method public static androidx.ink.brush.BrushFamily getPressurePenLatest();
+ method public static androidx.ink.brush.BrushFamily getPressurePenV1();
+ property public static final androidx.ink.brush.BrushFamily highlighterLatest;
+ property public static final androidx.ink.brush.BrushFamily highlighterV1;
+ property public static final androidx.ink.brush.BrushFamily markerLatest;
+ property public static final androidx.ink.brush.BrushFamily markerV1;
+ property public static final androidx.ink.brush.BrushFamily pressurePenLatest;
+ property public static final androidx.ink.brush.BrushFamily pressurePenV1;
+ field public static final androidx.ink.brush.StockBrushes INSTANCE;
}
}
diff --git a/ink/ink-brush/build.gradle b/ink/ink-brush/build.gradle
index 3beaaaa..1137891 100644
--- a/ink/ink-brush/build.gradle
+++ b/ink/ink-brush/build.gradle
@@ -27,12 +27,20 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
androidXMultiplatform {
- android()
jvm()
+ androidLibrary {
+ namespace = "androidx.ink.brush"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ compileSdk = 35
+ aarMetadata.minCompileSdk = 35
+ }
defaultPlatform(PlatformIdentifier.JVM)
@@ -47,13 +55,17 @@
implementation(libs.kotlinStdlib)
api(libs.androidx.annotation)
implementation(project(":collection:collection"))
+ implementation(project(":ink:ink-geometry"))
+ implementation(project(":ink:ink-nativeloader"))
}
}
jvmAndroidTest {
dependsOn(commonTest)
dependencies {
+ implementation(libs.junit)
implementation(libs.kotlinTest)
+ implementation(libs.truth)
}
}
@@ -64,7 +76,12 @@
androidInstrumentedTest {
dependsOn(jvmAndroidTest)
dependencies {
+ implementation(libs.testExtJunit)
+ implementation(libs.testRules)
implementation(libs.testRunner)
+ implementation(libs.espressoCore)
+ implementation(libs.junit)
+ implementation(libs.truth)
}
}
@@ -78,11 +95,6 @@
}
}
-android {
- compileSdk 35
- namespace = "androidx.ink.brush"
-}
-
androidx {
name = "Ink Brush"
type = LibraryType.PUBLISHED_LIBRARY
diff --git a/ink/ink-brush/lint-baseline.xml b/ink/ink-brush/lint-baseline.xml
new file mode 100644
index 0000000..73ba869
--- /dev/null
+++ b/ink/ink-brush/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
+
+ <issue
+ id="Range"
+ message="Expected length ≥ 1 (was 0)"
+ errorLine1=" Rgb("", FloatArray(6), WhitePoint(0f, 0f), sIdentity, sIdentity, 0.0f, 1.0f)"
+ errorLine2=" ~~">
+ <location
+ file="src/jvmAndroidTest/kotlin/androidx/ink/brush/color/ColorSpaceTest.kt"/>
+ </issue>
+
+</issues>
diff --git a/ink/ink-brush/src/androidInstrumentedTest/kotlin/androidx/ink/brush/BrushExtensionsTest.kt b/ink/ink-brush/src/androidInstrumentedTest/kotlin/androidx/ink/brush/BrushExtensionsTest.kt
new file mode 100644
index 0000000..b6b5bea
--- /dev/null
+++ b/ink/ink-brush/src/androidInstrumentedTest/kotlin/androidx/ink/brush/BrushExtensionsTest.kt
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import android.graphics.Color as AndroidColor
+import android.graphics.ColorSpace as AndroidColorSpace
+import android.os.Build
+import androidx.annotation.ColorLong
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RunWith(AndroidJUnit4::class)
+class BrushExtensionsTest {
+ private val displayP3 = AndroidColorSpace.get(AndroidColorSpace.Named.DISPLAY_P3)
+ private val adobeRgb = AndroidColorSpace.get(AndroidColorSpace.Named.ADOBE_RGB)
+
+ private val testColor = AndroidColor.valueOf(0.4f, 0.6f, 0.8f, 0.2f, displayP3)
+ @ColorLong private val testColorLong = testColor.pack()
+
+ @OptIn(ExperimentalInkCustomBrushApi::class)
+ private val testFamily = BrushFamily(uri = "/brush-family:pencil")
+
+ @Test
+ fun brushGetAndroidColor_getsCorrectColor() {
+ val brush = Brush.createWithColorLong(testFamily, testColorLong, 1f, 1f)
+
+ // Note that expectedColor is not necessarily the same as testColor, because of precision
+ // loss
+ // when converting from testColor to testColorLong (which is necessary, because Brush stores
+ // the
+ // color internally as a ColorLong anyway).
+ val expectedColor = AndroidColor.valueOf(testColorLong)
+ assertThat(brush.getAndroidColor()).isEqualTo(expectedColor)
+ }
+
+ @Test
+ fun brushCopyWithAndroidColor_setsColor() {
+ val brush = Brush.createWithColorIntArgb(testFamily, 0x4499bb66, 1f, 1f)
+
+ val newBrush = brush.copyWithAndroidColor(color = testColor)
+
+ assertThat(newBrush.family).isEqualTo(brush.family)
+ assertThat(newBrush.colorLong).isEqualTo(testColorLong)
+ assertThat(newBrush.size).isEqualTo(brush.size)
+ assertThat(newBrush.epsilon).isEqualTo(brush.epsilon)
+ }
+
+ @OptIn(ExperimentalInkCustomBrushApi::class)
+ @Test
+ fun brushCopyWithAndroidColor_andOtherChangedValues_createsBrushWithColor() {
+ val brush = Brush.createWithColorIntArgb(testFamily, 0x4499bb66, 1f, 1f)
+
+ val newBrush =
+ brush.copyWithAndroidColor(
+ color = testColor,
+ family = BrushFamily(),
+ size = 2f,
+ epsilon = 0.2f,
+ )
+
+ assertThat(newBrush.family).isEqualTo(BrushFamily())
+ assertThat(newBrush.colorLong).isEqualTo(testColorLong)
+ assertThat(newBrush.size).isEqualTo(2f)
+ assertThat(newBrush.epsilon).isEqualTo(0.2f)
+ }
+
+ @Test
+ fun brushCopyWithAndroidColor_withUnsupportedColorSpace_setsConvertedColor() {
+ val brush = Brush.createWithColorIntArgb(testFamily, 0x4499bb66, 1f, 1f)
+
+ val newColor = AndroidColor.valueOf(0.6f, 0.7f, 0.4f, 0.3f, adobeRgb)
+ val newBrush = brush.copyWithAndroidColor(color = newColor)
+
+ // newColor gets converted to ColorLong (losing precision) and then to Display P3.
+ val expectedColor = AndroidColor.valueOf(newColor.pack()).convert(displayP3)
+ assertThat(newBrush.colorLong).isEqualTo(expectedColor.pack())
+ }
+
+ @Test
+ fun brushBuilderAndroidColor_setsColor() {
+ val brush =
+ Brush.builder()
+ .setFamily(testFamily)
+ .setAndroidColor(testColor)
+ .setSize(1f)
+ .setEpsilon(1f)
+ .build()
+
+ assertThat(brush.colorLong).isEqualTo(testColorLong)
+ }
+
+ @Test
+ fun brushBuilderAndroidColor_withUnsupportedColorSpace_setsConvertedColor() {
+ val unsupportedColor = AndroidColor.valueOf(0.6f, 0.7f, 0.4f, 0.3f, adobeRgb)
+ val brush =
+ Brush.builder()
+ .setFamily(testFamily)
+ .setAndroidColor(unsupportedColor)
+ .setSize(1f)
+ .setEpsilon(1f)
+ .build()
+
+ // unsupportedColor gets converted to ColorLong (losing precision) and then to Display P3.
+ val expectedColor = AndroidColor.valueOf(unsupportedColor.pack()).convert(displayP3)
+ assertThat(brush.colorLong).isEqualTo(expectedColor.pack())
+ }
+
+ @Test
+ fun brushWithAndroidColor_createsBrushWithColor() {
+ val brush = Brush.withAndroidColor(testFamily, testColor, 1f, 1f)
+ assertThat(brush.colorLong).isEqualTo(testColorLong)
+ }
+
+ @Test
+ fun brushWithAndroidColor_withUnsupportedColorSpace_createsBrushWithConvertedColor() {
+ val unsupportedColor = AndroidColor.valueOf(0.6f, 0.7f, 0.4f, 0.3f, adobeRgb)
+ val brush = Brush.withAndroidColor(testFamily, unsupportedColor, 1f, 1f)
+
+ // unsupportedColor gets converted to ColorLong (losing precision) and then to Display P3.
+ val expectedColor = AndroidColor.valueOf(unsupportedColor.pack()).convert(displayP3)
+ assertThat(brush.colorLong).isEqualTo(expectedColor.pack())
+ }
+
+ @Test
+ fun brushUtilGetAndroidColor_getsCorrectColor() {
+ val brush = Brush.createWithColorLong(testFamily, testColorLong, 1f, 1f)
+
+ // Note that expectedColor is not necessarily the same as testColor, because of precision
+ // loss
+ // when converting from testColor to testColorLong.
+ val expectedColor = AndroidColor.valueOf(testColorLong)
+ assertThat(BrushUtil.getAndroidColor(brush)).isEqualTo(expectedColor)
+ }
+
+ @Test
+ fun brushUtilToBuilderWithAndroidColor_setsColor() {
+ val brush = Brush.createWithColorIntArgb(testFamily, 0x4499bb66, 2f, 0.2f)
+
+ val newBrush = BrushUtil.toBuilderWithAndroidColor(brush, testColor).build()
+
+ assertThat(newBrush.colorLong).isEqualTo(testColorLong)
+ assertThat(brush.family).isEqualTo(testFamily)
+ assertThat(brush.size).isEqualTo(2f)
+ assertThat(brush.epsilon).isEqualTo(0.2f)
+ }
+
+ @Test
+ fun brushUtilToBuilderWithAndroidColor_withUnsupportedColorSpace_setsConvertedColor() {
+ val brush = Brush.createWithColorIntArgb(testFamily, 0x4499bb66, 2f, 0.2f)
+
+ val unsupportedColor = AndroidColor.valueOf(0.6f, 0.7f, 0.4f, 0.3f, adobeRgb)
+ val newBrush = BrushUtil.toBuilderWithAndroidColor(brush, unsupportedColor).build()
+
+ // unsupportedColor gets converted to ColorLong (losing precision) and then to Display P3.
+ val expectedColor = AndroidColor.valueOf(unsupportedColor.pack()).convert(displayP3)
+ assertThat(newBrush.colorLong).isEqualTo(expectedColor.pack())
+
+ assertThat(brush.family).isEqualTo(testFamily)
+ assertThat(brush.size).isEqualTo(2f)
+ assertThat(brush.epsilon).isEqualTo(0.2f)
+ }
+
+ @Test
+ fun brushUtilMakeBuilderWithAndroidColor_setsColor() {
+ val brush =
+ BrushUtil.makeBuilderWithAndroidColor(testColor)
+ .setFamily(testFamily)
+ .setSize(2f)
+ .setEpsilon(0.2f)
+ .build()
+
+ assertThat(brush.family).isEqualTo(testFamily)
+ assertThat(brush.colorLong).isEqualTo(testColorLong)
+ assertThat(brush.size).isEqualTo(2f)
+ assertThat(brush.epsilon).isEqualTo(0.2f)
+ }
+
+ @Test
+ fun brushUtilMakeBuilderAndroidColor_withUnsupportedColorSpace_setsConvertedColor() {
+ val unsupportedColor = AndroidColor.valueOf(0.6f, 0.7f, 0.4f, 0.3f, adobeRgb)
+ val brush =
+ BrushUtil.makeBuilderWithAndroidColor(unsupportedColor)
+ .setFamily(testFamily)
+ .setSize(2f)
+ .setEpsilon(0.2f)
+ .build()
+
+ // unsupportedColor gets converted to ColorLong (losing precision) and then to Display P3.
+ val expectedColor = AndroidColor.valueOf(unsupportedColor.pack()).convert(displayP3)
+ assertThat(brush.colorLong).isEqualTo(expectedColor.pack())
+ }
+
+ @Test
+ fun brushUtilMakeBrushWithAndroidColor_createsBrushWithColor() {
+ val brush = BrushUtil.makeBrushWithAndroidColor(testFamily, testColor, 1f, 1f)
+ assertThat(brush.colorLong).isEqualTo(testColorLong)
+ }
+
+ @Test
+ fun brushUtilMakeBrushWithAndroidColor_withUnsupportedColorSpace_createsBrushWithConvertedColor() {
+ val unsupportedColor = AndroidColor.valueOf(0.6f, 0.7f, 0.4f, 0.3f, adobeRgb)
+ val brush = BrushUtil.makeBrushWithAndroidColor(testFamily, unsupportedColor, 1f, 1f)
+
+ // unsupportedColor gets converted to ColorLong (losing precision) and then to Display P3.
+ val expectedColor = AndroidColor.valueOf(unsupportedColor.pack()).convert(displayP3)
+ assertThat(brush.colorLong).isEqualTo(expectedColor.pack())
+ }
+}
diff --git a/ink/ink-brush/src/androidMain/kotlin/androidx/ink/brush/BrushExtensions.android.kt b/ink/ink-brush/src/androidMain/kotlin/androidx/ink/brush/BrushExtensions.android.kt
new file mode 100644
index 0000000..c7dd5f7
--- /dev/null
+++ b/ink/ink-brush/src/androidMain/kotlin/androidx/ink/brush/BrushExtensions.android.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+
+package androidx.ink.brush
+
+import android.graphics.Color as AndroidColor
+import android.os.Build
+import androidx.annotation.CheckResult
+import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
+
+/**
+ * The brush color as an [android.graphics.Color] instance, which can express colors in several
+ * different color spaces. sRGB and Display P3 are supported; a color in any other color space will
+ * be converted to Display P3.
+ */
+@JvmSynthetic
+@CheckResult
+@RequiresApi(Build.VERSION_CODES.O)
+public fun Brush.getAndroidColor(): AndroidColor = BrushUtil.getAndroidColor(this)
+
+/**
+ * Creates a copy of `this` [Brush] and allows named properties to be altered while keeping the rest
+ * unchanged. The color is specified as an [android.graphics.Color] instance, which can encode
+ * several different color spaces. sRGB and Display P3 are supported; a color in any other color
+ * space will be converted to Display P3.
+ */
+@JvmSynthetic
+@CheckResult
+@RequiresApi(Build.VERSION_CODES.O)
+public fun Brush.copyWithAndroidColor(
+ color: AndroidColor,
+ family: BrushFamily = this.family,
+ size: Float = this.size,
+ epsilon: Float = this.epsilon,
+): Brush = copyWithColorLong(color.pack(), family, size, epsilon)
+
+/**
+ * Set the color on a [Brush.Builder] as an [android.graphics.Color] instance. sRGB and Display P3
+ * are supported; a color in any other color space will be converted to Display P3.
+ */
+@JvmSynthetic
+@CheckResult
+@RequiresApi(Build.VERSION_CODES.O)
+public fun Brush.Builder.setAndroidColor(color: AndroidColor): Brush.Builder =
+ setColorLong(color.pack())
+
+/**
+ * Returns a new [Brush] with the color specified by an [android.graphics.Color] instance, which can
+ * encode several different color spaces. sRGB and Display P3 are supported; a color in any other
+ * color space will be converted to Display P3.
+ */
+@JvmSynthetic
+@CheckResult
+@RequiresApi(Build.VERSION_CODES.O)
+public fun Brush.Companion.withAndroidColor(
+ family: BrushFamily,
+ color: AndroidColor,
+ size: Float,
+ epsilon: Float,
+): Brush = BrushUtil.makeBrushWithAndroidColor(family, color, size, epsilon)
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+public object BrushUtil {
+
+ /**
+ * The brush color as an [android.graphics.Color] instance, which can express colors in several
+ * different color spaces. sRGB and Display P3 are supported; a color in any other color space
+ * will be converted to Display P3.
+ */
+ @JvmStatic
+ @CheckResult
+ @RequiresApi(Build.VERSION_CODES.O)
+ public fun getAndroidColor(brush: Brush): AndroidColor = AndroidColor.valueOf(brush.colorLong)
+
+ /**
+ * Returns a [Brush.Builder] with values set equivalent to [brush] and the color specified by an
+ * [android.graphics.Color] instance, which can encode several different color spaces. sRGB and
+ * Display P3 are supported; a color in any other color space will be converted to Display P3.
+ * Java developers, use the returned builder to build a copy of a Brush. Kotlin developers, see
+ * [copyWithAndroidColor] method.
+ */
+ @JvmStatic
+ @CheckResult
+ @RequiresApi(Build.VERSION_CODES.O)
+ public fun toBuilderWithAndroidColor(brush: Brush, color: AndroidColor): Brush.Builder =
+ brush.toBuilder().setAndroidColor(color)
+
+ /**
+ * Returns a new [Brush.Builder] with the color specified by an [android.graphics.Color]
+ * instance, which can encode several different color spaces. sRGB and Display P3 are supported;
+ * a color in any other color space will be converted to Display P3.
+ */
+ @JvmStatic
+ @CheckResult
+ @RequiresApi(Build.VERSION_CODES.O)
+ public fun makeBuilderWithAndroidColor(color: AndroidColor): Brush.Builder =
+ Brush.Builder().setAndroidColor(color)
+
+ /**
+ * Returns a new [Brush] with the color specified by an [android.graphics.Color] instance, which
+ * can encode several different color spaces. sRGB and Display P3 are supported; a color in any
+ * other color space will be converted to Display P3.
+ */
+ @JvmStatic
+ @CheckResult
+ @RequiresApi(Build.VERSION_CODES.O)
+ public fun makeBrushWithAndroidColor(
+ family: BrushFamily,
+ color: AndroidColor,
+ size: Float,
+ epsilon: Float,
+ ): Brush = Brush.createWithColorLong(family, color.pack(), size, epsilon)
+}
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/Brush.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/Brush.kt
index e1d6502..6ddee13 100644
--- a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/Brush.kt
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/Brush.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * Copyright (C) 2024 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,20 +16,365 @@
package androidx.ink.brush
+import androidx.annotation.ColorInt
+import androidx.annotation.ColorLong
+import androidx.annotation.FloatRange
+import androidx.annotation.RestrictTo
import androidx.ink.brush.color.Color as ComposeColor
+import androidx.ink.brush.color.toArgb
+import androidx.ink.nativeloader.NativeLoader
+import kotlin.Float
+import kotlin.jvm.JvmStatic
/**
* Defines how stroke inputs are interpreted to create the visual representation of a stroke.
*
- * A [Brush] completely describes how inputs are used to create stroke meshes, and how those meshes
- * should be drawn by stroke renderers.
- *
- * Note: This is a placeholder implementation/API until the real code is migrated here.
- *
- * @param color The color of the stroke.
- * @param size The overall thickness of strokes created with a given brush, in the same units as the
- * stroke coordinate system.
+ * The type completely describes how inputs are used to create stroke meshes, and how those meshes
+ * should be drawn by stroke renderers. In an analogous way to "font" and "font family", a [Brush]
+ * can be considered an instance of a [BrushFamily] with a particular [color], [size], and an extra
+ * parameter controlling visual fidelity, called [epsilon].
*/
-public class Brush(public val color: Long, public val size: Float) {
- private val colorObj = ComposeColor(color.toULong())
+@Suppress("NotCloseable") // Finalize is only used to free the native peer.
+public class Brush
+internal constructor(
+ /** The [BrushFamily] for this brush. See [StockBrushes] for available [BrushFamily] values. */
+ public val family: BrushFamily,
+ composeColor: ComposeColor,
+ /**
+ * The overall thickness of strokes created with a given brush, in the same units as the stroke
+ * coordinate system. This must be at least as big as [epsilon].
+ */
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = false,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false
+ )
+ public val size: Float,
+ /**
+ * The smallest distance for which two points should be considered visually distinct for stroke
+ * generation geometry purposes. Effectively, it is the visual fidelity of strokes created with
+ * this brush, where any (lack of) visual fidelity can be observed by a user the further zoomed
+ * in they are on the stroke. Lower values of [epsilon] result in higher fidelity strokes at the
+ * cost of somewhat higher memory usage. This value, like [size], is in the same units as the
+ * stroke coordinate system. A size of 0.1 physical pixels at the default zoom level is a good
+ * starting point that can tolerate a reasonable amount of zooming in with high quality visual
+ * results.
+ */
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = false,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false
+ )
+ public val epsilon: Float,
+) {
+
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public val composeColor: ComposeColor = composeColor.toColorInInkSupportedColorSpace()
+
+ /**
+ * The default color of a [Brush] is pure black. To set a custom color, use
+ * [createWithColorLong] or [createWithColorIntArgb].
+ */
+ public constructor(
+ family: BrushFamily,
+ size: Float,
+ epsilon: Float,
+ ) : this(family, DEFAULT_COMPOSE_COLOR, size, epsilon)
+
+ /**
+ * The brush color as a [ColorLong], which can express colors in several different color spaces.
+ * sRGB and Display P3 are supported; a color in any other color space will be converted to
+ * Display P3.
+ */
+ public val colorLong: Long
+ @ColorLong get(): Long = composeColor.value.toLong()
+
+ /**
+ * The brush color as a [ColorInt], which can only express colors in the sRGB color space. For
+ * clients that want to support wide-gamut colors, use [colorLong].
+ */
+ public val colorIntArgb: Int
+ @ColorInt get(): Int = composeColor.toArgb()
+
+ /** A handle to the underlying native [Brush] object. */
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public val nativePointer: Long =
+ nativeCreateBrush(
+ family.nativePointer,
+ this.composeColor.red,
+ this.composeColor.green,
+ this.composeColor.blue,
+ this.composeColor.alpha,
+ this.composeColor.colorSpace.toInkColorSpaceId(),
+ size,
+ epsilon,
+ )
+
+ // Base implementation of copy() that all public versions call.
+ private fun copy(family: BrushFamily, color: ComposeColor, size: Float, epsilon: Float): Brush {
+ return if (
+ family == this.family &&
+ color == this.composeColor &&
+ size == this.size &&
+ epsilon == this.epsilon
+ ) {
+ // For a pure copy, return the same object, since it is immutable.
+ this
+ } else {
+ Brush(family, color, size, epsilon)
+ }
+ }
+
+ /**
+ * Creates a copy of `this` and allows named properties to be altered while keeping the rest
+ * unchanged. To change the color, use [copyWithColorLong] or [copyWithColorIntArgb].
+ */
+ @JvmOverloads
+ public fun copy(
+ family: BrushFamily = this.family,
+ size: Float = this.size,
+ epsilon: Float = this.epsilon,
+ ): Brush = copy(family, this.composeColor, size, epsilon)
+
+ /**
+ * Creates a copy of `this` and allows named properties to be altered while keeping the rest
+ * unchanged. The color is specified as a [ColorLong], which can encode several different color
+ * spaces. sRGB and Display P3 are supported; a color in any other color space will be converted
+ * to Display P3.
+ *
+ * Some libraries (notably Jetpack UI Graphics) use [ULong] for [ColorLong]s, so the caller must
+ * call [ULong.toLong] on such a value before passing it to this method.
+ */
+ @JvmOverloads
+ public fun copyWithColorLong(
+ @ColorLong colorLong: Long,
+ family: BrushFamily = this.family,
+ size: Float = this.size,
+ epsilon: Float = this.epsilon,
+ ): Brush = copy(family, ComposeColor(colorLong.toULong()), size, epsilon)
+
+ /**
+ * Creates a copy of `this` and allows named properties to be altered while keeping the rest
+ * unchanged. The color is specified as a [ColorInt], which is in the sRGB color space by
+ * definition. Note that the [ColorInt] channel order puts alpha first (in the most significant
+ * byte).
+ *
+ * Kotlin interprets integer literals greater than `0x7fffffff` as [Long]s, so callers that want
+ * to specify a literal [ColorInt] with alpha >= 0x80 must call [Long.toInt] on the literal.
+ */
+ @JvmOverloads
+ public fun copyWithColorIntArgb(
+ @ColorInt colorIntArgb: Int,
+ family: BrushFamily = this.family,
+ size: Float = this.size,
+ epsilon: Float = this.epsilon,
+ ): Brush = copy(family, ComposeColor(colorIntArgb), size, epsilon)
+
+ /**
+ * Returns a [Builder] with values set equivalent to `this`. Java developers, use the returned
+ * builder to build a copy of a Brush. Kotlin developers, see [copy] method.
+ */
+ public fun toBuilder(): Builder =
+ Builder().setFamily(family).setComposeColor(composeColor).setSize(size).setEpsilon(epsilon)
+
+ /**
+ * Builder for [Brush].
+ *
+ * Use Brush.Builder to construct a [Brush] with default values, overriding only as needed.
+ */
+ public class Builder {
+ private var family: BrushFamily? = null
+ private var composeColor: ComposeColor = DEFAULT_COMPOSE_COLOR
+
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = false,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false,
+ )
+ private var size: Float? = null
+
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = false,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false,
+ )
+ private var epsilon: Float? = null
+
+ /**
+ * Sets the [BrushFamily] for this brush. See [StockBrushes] for available [BrushFamily]
+ * values.
+ */
+ public fun setFamily(family: BrushFamily): Builder {
+ this.family = family
+ return this
+ }
+
+ internal fun setComposeColor(color: ComposeColor): Builder {
+ this.composeColor = color
+ return this
+ }
+
+ /**
+ * Sets the color using a [ColorLong], which can encode several different color spaces. sRGB
+ * and Display P3 are supported; a color in any other color space will be converted to
+ * Display P3.
+ *
+ * Some libraries (notably Jetpack UI Graphics) use [ULong] for [ColorLong]s, so the caller
+ * must call [ULong.toLong] on such a value before passing it to this method.
+ */
+ public fun setColorLong(@ColorLong colorLong: Long): Builder {
+ this.composeColor = ComposeColor(colorLong.toULong())
+ return this
+ }
+
+ /**
+ * Sets the color using a [ColorInt], which is in the sRGB color space by definition. Note
+ * that the [ColorInt] channel order puts alpha first (in the most significant byte).
+ *
+ * Kotlin interprets integer literals greater than `0x7fffffff` as [Long]s, so Kotlin
+ * callers that want to specify a literal [ColorInt] with alpha >= 0x80 must call
+ * [Long.toInt] on the literal.
+ */
+ public fun setColorIntArgb(@ColorInt colorIntArgb: Int): Builder {
+ this.composeColor = ComposeColor(colorIntArgb)
+ return this
+ }
+
+ public fun setSize(
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = false,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false,
+ )
+ size: Float
+ ): Builder {
+ this.size = size
+ return this
+ }
+
+ public fun setEpsilon(
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = false,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false,
+ )
+ epsilon: Float
+ ): Builder {
+ this.epsilon = epsilon
+ return this
+ }
+
+ public fun build(): Brush =
+ Brush(
+ family =
+ checkNotNull(family) {
+ "brush family must be specified before calling build()"
+ },
+ composeColor = composeColor,
+ size = checkNotNull(size) { "brush size must be specified before calling build()" },
+ epsilon =
+ checkNotNull(epsilon) {
+ "brush epsilon must be specified before calling build()"
+ },
+ )
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Brush) return false
+
+ if (family != other.family) return false
+ if (composeColor != other.composeColor) return false
+ if (size != other.size) return false
+ if (epsilon != other.epsilon) return false
+
+ return true
+ }
+
+ // NOMUTANTS -- not testing exact hashCode values, just that equality implies the same hashCode.
+ override fun hashCode(): Int {
+ var result = family.hashCode()
+ result = 31 * result + composeColor.hashCode()
+ result = 31 * result + size.hashCode()
+ result = 31 * result + epsilon.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "Brush(family=$family, color=$composeColor, size=$size, epsilon=$epsilon)"
+ }
+
+ /** Delete native Brush memory. */
+ protected fun finalize() {
+ // NOMUTANTS -- Not tested post garbage collection.
+ nativeFreeBrush(nativePointer)
+ }
+
+ /** Create underlying native object and return reference for all subsequent native calls. */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeCreateBrush(
+ familyNativePointer: Long,
+ colorRed: Float,
+ colorGreen: Float,
+ colorBlue: Float,
+ colorAlpha: Float,
+ colorSpace: Int,
+ size: Float,
+ epsilon: Float,
+ ): Long
+
+ /** Release the underlying memory allocated in [nativeCreateBrush]. */
+ private external fun nativeFreeBrush(
+ nativePointer: Long
+ ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ public companion object {
+ init {
+ NativeLoader.load()
+ }
+
+ private val DEFAULT_COMPOSE_COLOR = ComposeColor.Black
+
+ /**
+ * Returns a new [Brush] with the color specified by a [ColorLong], which can encode several
+ * different color spaces. sRGB and Display P3 are supported; a color in any other color
+ * space will be converted to Display P3.
+ *
+ * Some libraries (notably Jetpack UI Graphics) use [ULong] for [ColorLong]s, so the caller
+ * must call [ULong.toLong] on such a value before passing it to this method.
+ */
+ @JvmStatic
+ public fun createWithColorLong(
+ family: BrushFamily,
+ @ColorLong colorLong: Long,
+ size: Float,
+ epsilon: Float,
+ ): Brush = Brush(family, ComposeColor(colorLong.toULong()), size, epsilon)
+
+ /**
+ * Returns a new [Brush] with the color specified by a [ColorInt], which is in the sRGB
+ * color space by definition. Note that the [ColorInt] channel order puts alpha first (in
+ * the most significant byte).
+ *
+ * Kotlin interprets integer literals greater than `0x7fffffff` as [Long]s, so callers that
+ * want to specify a literal [ColorInt] with alpha >= 0x80 must call [Long.toInt] on the
+ * literal.
+ */
+ @JvmStatic
+ public fun createWithColorIntArgb(
+ family: BrushFamily,
+ @ColorInt colorIntArgb: Int,
+ size: Float,
+ epsilon: Float,
+ ): Brush = Brush(family, ComposeColor(colorIntArgb), size, epsilon)
+
+ /** Returns a new [Brush.Builder]. */
+ @JvmStatic public fun builder(): Builder = Builder()
+ }
}
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushBehavior.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushBehavior.kt
new file mode 100644
index 0000000..3b7d766
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushBehavior.kt
@@ -0,0 +1,1485 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.annotation.RestrictTo
+import androidx.ink.nativeloader.NativeLoader
+import java.util.Collections.unmodifiableList
+import java.util.Collections.unmodifiableSet
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
+import kotlin.jvm.JvmStatic
+import kotlin.reflect.KClass
+
+/**
+ * A behavior describing how stroke input properties should affect the shape and color of the brush
+ * tip.
+ *
+ * The behavior is conceptually a graph made from the various node types defined below. Each edge of
+ * the graph represents passing a nullable floating point value between nodes, and each node in the
+ * graph fits into one of the following categories:
+ * 1. Leaf nodes generate an output value without graph inputs. For example, they can create a value
+ * from properties of stroke input.
+ * 2. Filter nodes can conditionally toggle branches of the graph "on" by outputting their input
+ * value, or "off" by outputting a null value.
+ * 3. Operator nodes take in one or more input values and generate an output. For example, by
+ * mapping input to output with an easing function.
+ * 4. Target nodes apply an input value to chosen properties of the brush tip.
+ *
+ * For each input in a stroke, [BrushTip.behaviors] are applied as follows:
+ * 1. The actual target modifier (as calculated above) for each tip property is accumulated from
+ * every [BrushBehavior] present on the current [BrushTip]. Multiple behaviors can affect the
+ * same [Target]. Depending on the [Target], modifiers from multiple behaviors will stack either
+ * additively or multiplicatively, according to the documentation for that [Target]. Regardless,
+ * the order of specified behaviors does not affect the result.
+ * 2. The modifiers are applied to the shape and color shift values of the tip's state according to
+ * the documentation for each [Target]. The resulting tip property values are then clamped or
+ * normalized to within their valid range of values. E.g. the final value of
+ * [BrushTip.cornerRounding] will be clamped within [0, 1]. Generally: The affected shape values
+ * are those found in [BrushTip] members. The color shift values remain in the range -100% to
+ * +100%. Note that when stored on a vertex, the color shift is encoded such that each channel is
+ * in the range [0, 1], where 0.5 represents a 0% shift.
+ *
+ * Note that the accumulated tip shape property modifiers may be adjusted by the implementation
+ * before being applied: The rates of change of shape properties may be constrained to keep them
+ * from changing too rapidly with respect to distance traveled from one input to the next.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+@ExperimentalInkCustomBrushApi
+// NotCloseable: Finalize is only used to free the native peer.
+// Deprecation: b/356424519 Migrate to targetNodes
+@Suppress("NotCloseable", "DEPRECATION")
+public class BrushBehavior(
+ // The [targetNodes] val below is a defensive copy of this parameter.
+ targetNodes: List<TargetNode>
+) {
+ public val targetNodes: List<TargetNode> = unmodifiableList(targetNodes.toList())
+
+ /** A handle to the underlying native [BrushBehavior] object. */
+ internal val nativePointer: Long = createNativeBrushBehavior(targetNodes)
+
+ /**
+ * Constructs a simple [BrushBehavior] using whatever [Node]s are necessary for the specified
+ * fields.
+ */
+ public constructor(
+ source: Source,
+ target: Target,
+ sourceValueRangeLowerBound: Float,
+ sourceValueRangeUpperBound: Float,
+ targetModifierRangeLowerBound: Float,
+ targetModifierRangeUpperBound: Float,
+ sourceOutOfRangeBehavior: OutOfRange = OutOfRange.CLAMP,
+ responseCurve: EasingFunction = EasingFunction.Predefined.LINEAR,
+ responseTimeMillis: Long = 0L,
+ enabledToolTypes: Set<InputToolType> = ALL_TOOL_TYPES,
+ isFallbackFor: OptionalInputProperty? = null,
+ ) : this(
+ run<List<TargetNode>> {
+ var node: ValueNode =
+ SourceNode(
+ source,
+ sourceValueRangeLowerBound,
+ sourceValueRangeUpperBound,
+ sourceOutOfRangeBehavior,
+ )
+ if (enabledToolTypes != ALL_TOOL_TYPES) {
+ node = ToolTypeFilterNode(enabledToolTypes, node)
+ }
+ if (isFallbackFor != null) {
+ node = FallbackFilterNode(isFallbackFor, node)
+ }
+ // [EasingFunction.Predefined.LINEAR] is the identity function, so no need to add a
+ // [ResponseNode] with that function.
+ if (responseCurve != EasingFunction.Predefined.LINEAR) {
+ node = ResponseNode(responseCurve, node)
+ }
+ if (responseTimeMillis != 0L) {
+ node =
+ DampingNode(
+ DampingSource.TIME_IN_SECONDS,
+ responseTimeMillis.toFloat() / 1000.0f,
+ node
+ )
+ }
+ listOf(
+ TargetNode(
+ target,
+ targetModifierRangeLowerBound,
+ targetModifierRangeUpperBound,
+ node
+ )
+ )
+ }
+ )
+
+ /**
+ * Returns a node in the behavior of the given [Node] subclass, matching the given predicate (if
+ * any).
+ */
+ // TODO: b/356424519 - Remove this method once the below legacy properties are removed.
+ private fun <T : Node> findNode(
+ nodeClass: KClass<T>,
+ predicate: (T) -> Boolean = { true }
+ ): T? {
+ val stack = ArrayDeque<Node>(targetNodes)
+ while (!stack.isEmpty()) {
+ val node = stack.removeLast()
+ // [KClass.safeCast] is apparently discouraged on Android for performance reasons.
+ if (nodeClass.isInstance(node)) {
+ @Suppress("UNCHECKED_CAST") // cast is protected by enclosing if statement
+ val result = node as T
+ if (predicate(result)) return result
+ }
+ stack.addAll(node.inputs())
+ }
+ return null
+ }
+
+ // The below properties are implemented so as to give the correct answer in cases where the
+ // [BrushBehavior] was created using the legacy convenience constructor, and to give _some_ kind
+ // of plausible answer in the more general case of a [BrushBehavior] created with an arbitrary
+ // node graph. Once existing callers of these properties are migrated to working with [Node]s
+ // instead, we can remove them.
+ //
+ // TODO: b/356424519 - Remove the below getters once we no longer need them.
+
+ @Deprecated("Prefer using targetNodes instead.")
+ public val source: Source
+ get() = findNode(SourceNode::class)?.source ?: Source.NORMALIZED_PRESSURE
+
+ @Deprecated("Prefer using targetNodes instead.")
+ public val target: Target
+ get() = findNode(TargetNode::class)?.target ?: Target.SIZE_MULTIPLIER
+
+ @Deprecated("Prefer using targetNodes instead.")
+ public val sourceValueRangeLowerBound: Float
+ get() = findNode(SourceNode::class)?.sourceValueRangeLowerBound ?: 0.0f
+
+ @Deprecated("Prefer using targetNodes instead.")
+ public val sourceValueRangeUpperBound: Float
+ get() = findNode(SourceNode::class)?.sourceValueRangeUpperBound ?: 1.0f
+
+ @Deprecated("Prefer using targetNodes instead.")
+ public val targetModifierRangeLowerBound: Float
+ get() = findNode(TargetNode::class)?.targetModifierRangeLowerBound ?: 0.0f
+
+ @Deprecated("Prefer using targetNodes instead.")
+ public val targetModifierRangeUpperBound: Float
+ get() = findNode(TargetNode::class)?.targetModifierRangeUpperBound ?: 1.0f
+
+ @Deprecated("Prefer using targetNodes instead.")
+ public val sourceOutOfRangeBehavior: OutOfRange
+ get() = findNode(SourceNode::class)?.sourceOutOfRangeBehavior ?: OutOfRange.CLAMP
+
+ @Deprecated("Prefer using targetNodes instead.")
+ public val responseCurve: EasingFunction
+ get() = findNode(ResponseNode::class)?.responseCurve ?: EasingFunction.Predefined.LINEAR
+
+ @Deprecated("Prefer using targetNodes instead.")
+ public val responseTimeMillis: Long
+ get() =
+ ((findNode(DampingNode::class, { it.dampingSource == DampingSource.TIME_IN_SECONDS })
+ ?.dampingGap ?: 0.0f) * 1000.0f)
+ .toLong()
+
+ @Deprecated("Prefer using targetNodes instead.")
+ public val enabledToolTypes: Set<InputToolType>
+ get() = findNode(ToolTypeFilterNode::class)?.enabledToolTypes ?: ALL_TOOL_TYPES
+
+ @Deprecated("Prefer using targetNodes instead.")
+ public val isFallbackFor: OptionalInputProperty?
+ get() = findNode(FallbackFilterNode::class)?.isFallbackFor
+
+ /**
+ * Creates a copy of `this` and allows named properties to be altered while keeping the rest
+ * unchanged.
+ */
+ @JvmSynthetic
+ public fun copy(
+ source: Source = this.source,
+ target: Target = this.target,
+ sourceOutOfRangeBehavior: OutOfRange = this.sourceOutOfRangeBehavior,
+ sourceValueRangeLowerBound: Float = this.sourceValueRangeLowerBound,
+ sourceValueRangeUpperBound: Float = this.sourceValueRangeUpperBound,
+ targetModifierRangeLowerBound: Float = this.targetModifierRangeLowerBound,
+ targetModifierRangeUpperBound: Float = this.targetModifierRangeUpperBound,
+ responseCurve: EasingFunction = this.responseCurve,
+ responseTimeMillis: Long = this.responseTimeMillis,
+ enabledToolTypes: Set<InputToolType> = this.enabledToolTypes,
+ isFallbackFor: OptionalInputProperty? = this.isFallbackFor,
+ ): BrushBehavior =
+ BrushBehavior(
+ source,
+ target,
+ sourceValueRangeLowerBound,
+ sourceValueRangeUpperBound,
+ targetModifierRangeLowerBound,
+ targetModifierRangeUpperBound,
+ sourceOutOfRangeBehavior,
+ responseCurve,
+ responseTimeMillis,
+ enabledToolTypes,
+ isFallbackFor,
+ )
+
+ /**
+ * Returns a [Builder] with values set equivalent to `this`. Java developers, use the returned
+ * builder to build a copy of a BrushBehavior.
+ */
+ public fun toBuilder(): Builder =
+ Builder()
+ .setSource(source)
+ .setTarget(target)
+ .setSourceOutOfRangeBehavior(sourceOutOfRangeBehavior)
+ .setSourceValueRangeLowerBound(sourceValueRangeLowerBound)
+ .setSourceValueRangeUpperBound(sourceValueRangeUpperBound)
+ .setTargetModifierRangeLowerBound(targetModifierRangeLowerBound)
+ .setTargetModifierRangeUpperBound(targetModifierRangeUpperBound)
+ .setResponseCurve(responseCurve)
+ .setResponseTimeMillis(responseTimeMillis)
+ .setEnabledToolTypes(enabledToolTypes)
+ .setIsFallbackFor(isFallbackFor)
+
+ /**
+ * Builder for [BrushBehavior].
+ *
+ * For Java developers, use BrushBehavior.Builder to construct a [BrushBehavior] with default
+ * values, overriding only as needed. For example:
+ * ```
+ * BrushBehavior behavior = new BrushBehavior.Builder()
+ * .setSource(...)
+ * .setTarget(...)
+ * .setSourceOutOfRangeBehavior(...)
+ * .setSourceValueRangeLowerBound(...)
+ * .build();
+ * ```
+ */
+ @Suppress("ScopeReceiverThis")
+ public class Builder {
+ private var source: Source = Source.NORMALIZED_PRESSURE
+ private var target: Target = Target.SIZE_MULTIPLIER
+ private var sourceOutOfRangeBehavior: OutOfRange = OutOfRange.CLAMP
+ private var sourceValueRangeLowerBound: Float = 0f
+ private var sourceValueRangeUpperBound: Float = 1f
+ private var targetModifierRangeLowerBound: Float = 0f
+ private var targetModifierRangeUpperBound: Float = 1f
+ private var responseCurve: EasingFunction = EasingFunction.Predefined.LINEAR
+ private var responseTimeMillis: Long = 0L
+ private var enabledToolTypes: Set<InputToolType> = ALL_TOOL_TYPES
+ private var isFallbackFor: OptionalInputProperty? = null
+
+ public fun setSource(source: Source): Builder = apply { this.source = source }
+
+ public fun setTarget(target: Target): Builder = apply { this.target = target }
+
+ public fun setSourceOutOfRangeBehavior(sourceOutOfRangeBehavior: OutOfRange): Builder =
+ apply {
+ this.sourceOutOfRangeBehavior = sourceOutOfRangeBehavior
+ }
+
+ public fun setSourceValueRangeLowerBound(sourceValueRangeLowerBound: Float): Builder =
+ apply {
+ this.sourceValueRangeLowerBound = sourceValueRangeLowerBound
+ }
+
+ public fun setSourceValueRangeUpperBound(sourceValueRangeUpperBound: Float): Builder =
+ apply {
+ this.sourceValueRangeUpperBound = sourceValueRangeUpperBound
+ }
+
+ public fun setTargetModifierRangeLowerBound(targetModifierRangeLowerBound: Float): Builder =
+ apply {
+ this.targetModifierRangeLowerBound = targetModifierRangeLowerBound
+ }
+
+ public fun setTargetModifierRangeUpperBound(targetModifierRangeUpperBound: Float): Builder =
+ apply {
+ this.targetModifierRangeUpperBound = targetModifierRangeUpperBound
+ }
+
+ public fun setResponseCurve(responseCurve: EasingFunction): Builder = apply {
+ this.responseCurve = responseCurve
+ }
+
+ public fun setResponseTimeMillis(responseTimeMillis: Long): Builder = apply {
+ this.responseTimeMillis = responseTimeMillis
+ }
+
+ public fun setEnabledToolTypes(enabledToolTypes: Set<InputToolType>): Builder = apply {
+ this.enabledToolTypes = enabledToolTypes.toSet()
+ }
+
+ public fun setIsFallbackFor(isFallbackFor: OptionalInputProperty?): Builder = apply {
+ this.isFallbackFor = isFallbackFor
+ }
+
+ public fun build(): BrushBehavior =
+ BrushBehavior(
+ source,
+ target,
+ sourceValueRangeLowerBound,
+ sourceValueRangeUpperBound,
+ targetModifierRangeLowerBound,
+ targetModifierRangeUpperBound,
+ sourceOutOfRangeBehavior,
+ responseCurve,
+ responseTimeMillis,
+ enabledToolTypes,
+ isFallbackFor,
+ )
+ }
+
+ override fun equals(other: Any?): Boolean {
+ // NOMUTANTS -- Check the instance first to short circuit faster.
+ if (other === this) return true
+ if (other == null || other !is BrushBehavior) return false
+ return (source == other.source &&
+ target == other.target &&
+ sourceOutOfRangeBehavior == other.sourceOutOfRangeBehavior &&
+ sourceValueRangeLowerBound == other.sourceValueRangeLowerBound &&
+ sourceValueRangeUpperBound == other.sourceValueRangeUpperBound &&
+ targetModifierRangeLowerBound == other.targetModifierRangeLowerBound &&
+ targetModifierRangeUpperBound == other.targetModifierRangeUpperBound &&
+ responseCurve == other.responseCurve &&
+ responseTimeMillis == other.responseTimeMillis &&
+ enabledToolTypes == other.enabledToolTypes &&
+ isFallbackFor == other.isFallbackFor)
+ }
+
+ override fun hashCode(): Int {
+ var result = source.hashCode()
+ result = 31 * result + target.hashCode()
+ result = 31 * result + sourceOutOfRangeBehavior.hashCode()
+ result = 31 * result + sourceValueRangeLowerBound.hashCode()
+ result = 31 * result + sourceValueRangeUpperBound.hashCode()
+ result = 31 * result + targetModifierRangeLowerBound.hashCode()
+ result = 31 * result + targetModifierRangeUpperBound.hashCode()
+ result = 31 * result + responseCurve.hashCode()
+ result = 31 * result + responseTimeMillis.hashCode()
+ result = 31 * result + (isFallbackFor?.hashCode() ?: 0)
+ result = 31 * result + enabledToolTypes.hashCode()
+ return result
+ }
+
+ override fun toString(): String =
+ "BrushBehavior(source=$source, target=$target, " +
+ "sourceOutOfRangeBehavior=$sourceOutOfRangeBehavior, " +
+ "sourceValueRangeLowerBound=$sourceValueRangeLowerBound, " +
+ "sourceValueRangeUpperBound=$sourceValueRangeUpperBound, " +
+ "targetModifierRangeLowerBound=$targetModifierRangeLowerBound, " +
+ "targetModifierRangeUpperBound=$targetModifierRangeUpperBound, " +
+ "responseCurve=$responseCurve, responseTimeMillis=$responseTimeMillis, " +
+ "enabledToolTypes=$enabledToolTypes, isFallbackFor=$isFallbackFor)"
+
+ /** Delete native BrushBehavior memory. */
+ protected fun finalize() {
+ // NOMUTANTS -- Not tested post garbage collection.
+ nativeFreeBrushBehavior(nativePointer)
+ }
+
+ private fun createNativeBrushBehavior(targetNodes: List<TargetNode>): Long {
+ // TODO: b/356424519 - Use dup/swap nodes to avoid repeating common subexpressions.
+ val orderedNodes = ArrayDeque<Node>()
+ val stack = ArrayDeque<Node>(targetNodes)
+ while (!stack.isEmpty()) {
+ stack.removeLast().let { node ->
+ orderedNodes.addFirst(node)
+ stack.addAll(node.inputs())
+ }
+ }
+
+ val nativePointer = nativeCreateEmptyBrushBehavior()
+ for (node in orderedNodes) {
+ node.appendToNativeBrushBehavior(nativePointer)
+ }
+ return nativeValidateOrDeleteAndThrow(nativePointer)
+ }
+
+ /** Creates an underlying native brush behavior with no nodes and returns its memory address. */
+ private external fun nativeCreateEmptyBrushBehavior():
+ Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ /**
+ * Validates a native `BrushBehavior` and returns the pointer back, or deletes the native
+ * `BrushBehavior` and throws an exception if it's not valid.
+ */
+ private external fun nativeValidateOrDeleteAndThrow(
+ nativePointer: Long
+ ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ /**
+ * Release the underlying memory allocated in [nativeCreateBrushBehaviorLinear],
+ * [nativeCreateBrushBehaviorPredefined], [nativeCreateBrushBehaviorSteps], or
+ * [nativeCreateBrushBehaviorCubicBezier].
+ */
+ private external fun nativeFreeBrushBehavior(
+ nativePointer: Long
+ ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ public companion object {
+ init {
+ NativeLoader.load()
+ }
+
+ /** Returns a new [BrushBehavior.Builder]. */
+ @JvmStatic public fun builder(): Builder = Builder()
+
+ @JvmField
+ public val ALL_TOOL_TYPES: Set<InputToolType> =
+ setOf(
+ InputToolType.STYLUS,
+ InputToolType.UNKNOWN,
+ InputToolType.MOUSE,
+ InputToolType.TOUCH
+ )
+ }
+
+ /**
+ * List of input properties along with their units that can act as sources for a
+ * [BrushBehavior].
+ */
+ public class Source private constructor(@JvmField internal val value: Int) {
+ public fun toSimpleString(): String =
+ when (this) {
+ CONSTANT_ZERO -> "CONSTANT_ZERO"
+ NORMALIZED_PRESSURE -> "NORMALIZED_PRESSURE"
+ TILT_IN_RADIANS -> "TILT_IN_RADIANS"
+ TILT_X_IN_RADIANS -> "TILT_X_IN_RADIANS"
+ TILT_Y_IN_RADIANS -> "TILT_Y_IN_RADIANS"
+ ORIENTATION_IN_RADIANS -> "ORIENTATION_IN_RADIANS"
+ ORIENTATION_ABOUT_ZERO_IN_RADIANS -> "ORIENTATION_ABOUT_ZERO_IN_RADIANS"
+ SPEED_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND ->
+ "SPEED_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND"
+ VELOCITY_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND ->
+ "VELOCITY_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND"
+ VELOCITY_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND ->
+ "VELOCITY_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND"
+ DIRECTION_IN_RADIANS -> "DIRECTION_IN_RADIANS"
+ DIRECTION_ABOUT_ZERO_IN_RADIANS -> "DIRECTION_ABOUT_ZERO_IN_RADIANS"
+ NORMALIZED_DIRECTION_X -> "NORMALIZED_DIRECTION_X"
+ NORMALIZED_DIRECTION_Y -> "NORMALIZED_DIRECTION_Y"
+ DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE ->
+ "DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE"
+ TIME_OF_INPUT_IN_SECONDS -> "TIME_OF_INPUT_IN_SECONDS"
+ TIME_OF_INPUT_IN_MILLIS -> "TIME_OF_INPUT_IN_MILLIS"
+ PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE ->
+ "PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE"
+ PREDICTED_TIME_ELAPSED_IN_SECONDS -> "PREDICTED_TIME_ELAPSED_IN_SECONDS"
+ PREDICTED_TIME_ELAPSED_IN_MILLIS -> "PREDICTED_TIME_ELAPSED_IN_MILLIS"
+ DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE ->
+ "DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE"
+ TIME_SINCE_INPUT_IN_SECONDS -> "TIME_SINCE_INPUT_IN_SECONDS"
+ TIME_SINCE_INPUT_IN_MILLIS -> "TIME_SINCE_INPUT_IN_MILLIS"
+ ACCELERATION_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED ->
+ "ACCELERATION_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED"
+ ACCELERATION_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED ->
+ "ACCELERATION_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED"
+ ACCELERATION_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED ->
+ "ACCELERATION_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED"
+ ACCELERATION_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED ->
+ "ACCELERATION_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED"
+ ACCELERATION_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED ->
+ "ACCELERATION_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED"
+ INPUT_SPEED_IN_CENTIMETERS_PER_SECOND -> "INPUT_SPEED_IN_CENTIMETERS_PER_SECOND"
+ INPUT_VELOCITY_X_IN_CENTIMETERS_PER_SECOND ->
+ "INPUT_VELOCITY_X_IN_CENTIMETERS_PER_SECOND"
+ INPUT_VELOCITY_Y_IN_CENTIMETERS_PER_SECOND ->
+ "INPUT_VELOCITY_Y_IN_CENTIMETERS_PER_SECOND"
+ INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS -> "INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS"
+ PREDICTED_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS ->
+ "PREDICTED_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS"
+ INPUT_ACCELERATION_IN_CENTIMETERS_PER_SECOND_SQUARED ->
+ "INPUT_ACCELERATION_IN_CENTIMETERS_PER_SECOND_SQUARED"
+ INPUT_ACCELERATION_X_IN_CENTIMETERS_PER_SECOND_SQUARED ->
+ "INPUT_ACCELERATION_X_IN_CENTIMETERS_PER_SECOND_SQUARED"
+ INPUT_ACCELERATION_Y_IN_CENTIMETERS_PER_SECOND_SQUARED ->
+ "INPUT_ACCELERATION_Y_IN_CENTIMETERS_PER_SECOND_SQUARED"
+ INPUT_ACCELERATION_FORWARD_IN_CENTIMETERS_PER_SECOND_SQUARED ->
+ "INPUT_ACCELERATION_FORWARD_IN_CENTIMETERS_PER_SECOND_SQUARED"
+ INPUT_ACCELERATION_LATERAL_IN_CENTIMETERS_PER_SECOND_SQUARED ->
+ "INPUT_ACCELERATION_LATERAL_IN_CENTIMETERS_PER_SECOND_SQUARED"
+ else -> "INVALID"
+ }
+
+ override fun toString(): String = PREFIX + this.toSimpleString()
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is Source) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+
+ /**
+ * A source whose value is always zero. This can be used to provide a constant modifier
+ * to a target value. Normally this is not needed, because you can just set those
+ * modifiers directly on the [BrushTip], but it can become useful when combined with the
+ * [enabledToolTypes] and/or [isFallbackFor] fields to only conditionally enable it.
+ */
+ @JvmField public val CONSTANT_ZERO: Source = Source(0)
+ /** Stylus or touch pressure with values reported in the range [0, 1]. */
+ @JvmField public val NORMALIZED_PRESSURE: Source = Source(1)
+ /** Stylus tilt with values reported in the range [0, π/2] radians. */
+ @JvmField public val TILT_IN_RADIANS: Source = Source(2)
+ /**
+ * Stylus tilt along the x axis in the range [-π/2, π/2], with a positive value
+ * corresponding to tilt toward the respective positive axis. In order for those values
+ * to be reported, both tilt and orientation have to be populated on the StrokeInput.
+ */
+ @JvmField public val TILT_X_IN_RADIANS: Source = Source(3)
+ /**
+ * Stylus tilt along the y axis in the range [-π/2, π/2], with a positive value
+ * corresponding to tilt toward the respective positive axis. In order for those values
+ * to be reported, both tilt and orientation have to be populated on the StrokeInput.
+ */
+ @JvmField public val TILT_Y_IN_RADIANS: Source = Source(4)
+ /** Stylus orientation with values reported in the range [0, 2π). */
+ @JvmField public val ORIENTATION_IN_RADIANS: Source = Source(5)
+ /** Stylus orientation with values reported in the range (-π, π]. */
+ @JvmField public val ORIENTATION_ABOUT_ZERO_IN_RADIANS: Source = Source(6)
+ /**
+ * Pointer speed with values >= 0 in distance units per second, where one distance unit
+ * is equal to the brush size.
+ */
+ @JvmField public val SPEED_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND: Source = Source(7)
+ /**
+ * Signed x component of pointer velocity in distance units per second, where one
+ * distance unit is equal to the brush size.
+ */
+ @JvmField
+ public val VELOCITY_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND: Source = Source(8)
+ /**
+ * Signed y component of pointer velocity in distance units per second, where one
+ * distance unit is equal to the brush size.
+ */
+ @JvmField
+ public val VELOCITY_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND: Source = Source(9)
+ /**
+ * The angle of the stroke's current direction of travel in stroke space, normalized to
+ * the range [0, 2π). A value of 0 indicates the direction of the positive X-axis in
+ * stroke space; a value of π/2 indicates the direction of the positive Y-axis in stroke
+ * space.
+ */
+ @JvmField public val DIRECTION_IN_RADIANS: Source = Source(10)
+ /**
+ * The angle of the stroke's current direction of travel in stroke space, normalized to
+ * the range (-π, π]. A value of 0 indicates the direction of the positive X-axis in
+ * stroke space; a value of π/2 indicates the direction of the positive Y-axis in stroke
+ * space.
+ */
+ @JvmField public val DIRECTION_ABOUT_ZERO_IN_RADIANS: Source = Source(11)
+ /**
+ * Signed x component of the normalized travel direction, with values in the range
+ * [-1, 1].
+ */
+ @JvmField public val NORMALIZED_DIRECTION_X: Source = Source(12)
+ /**
+ * Signed y component of the normalized travel direction, with values in the range
+ * [-1, 1].
+ */
+ @JvmField public val NORMALIZED_DIRECTION_Y: Source = Source(13)
+ /**
+ * Distance traveled by the inputs of the current stroke, starting at 0 at the first
+ * input, where one distance unit is equal to the brush size.
+ */
+ @JvmField public val DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE: Source = Source(14)
+ /**
+ * The time elapsed, in seconds, from when the stroke started to when this part of the
+ * stroke was drawn. The value remains fixed for any given part of the stroke once
+ * drawn.
+ */
+ @JvmField public val TIME_OF_INPUT_IN_SECONDS: Source = Source(15)
+ /**
+ * The time elapsed, in millis, from when the stroke started to when this part of the
+ * stroke was drawn. The value remains fixed for any given part of the stroke once
+ * drawn.
+ */
+ @JvmField public val TIME_OF_INPUT_IN_MILLIS: Source = Source(16)
+ /**
+ * Distance traveled by the inputs of the current prediction, starting at 0 at the last
+ * non-predicted input, where one distance unit is equal to the brush size. For cases
+ * where prediction hasn't started yet, we don't return a negative value, but clamp to a
+ * min of 0.
+ */
+ @JvmField
+ public val PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE: Source = Source(17)
+ /**
+ * Elapsed time of the prediction, starting at 0 at the last non-predicted input. For
+ * cases where prediction hasn't started yet, we don't return a negative value, but
+ * clamp to a min of 0.
+ */
+ @JvmField public val PREDICTED_TIME_ELAPSED_IN_SECONDS: Source = Source(18)
+ /**
+ * Elapsed time of the prediction, starting at 0 at the last non-predicted input. For
+ * cases where prediction hasn't started yet, we don't return a negative value, but
+ * clamp to a min of 0.
+ */
+ @JvmField public val PREDICTED_TIME_ELAPSED_IN_MILLIS: Source = Source(19)
+ /**
+ * The distance left to be traveled from a given input to the current last input of the
+ * stroke, where one distance unit is equal to the brush size. This value changes for
+ * each input as the stroke is drawn.
+ */
+ @JvmField public val DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE: Source = Source(20)
+ /**
+ * The amount of time that has elapsed, in seconds, since this part of the stroke was
+ * drawn. This continues to increase even after all stroke inputs have completed, and
+ * can be used to drive stroke animations. This enumerators are only compatible with a
+ * [sourceOutOfRangeBehavior] of [OutOfRange.CLAMP], to ensure that the animation will
+ * eventually end.
+ */
+ @JvmField public val TIME_SINCE_INPUT_IN_SECONDS: Source = Source(21)
+ /**
+ * The amount of time that has elapsed, in millis, since this part of the stroke was
+ * drawn. This continues to increase even after all stroke inputs have completed, and
+ * can be used to drive stroke animations. This enumerators are only compatible with a
+ * [sourceOutOfRangeBehavior] of [OutOfRange.CLAMP], to ensure that the animation will
+ * eventually end.
+ */
+ @JvmField public val TIME_SINCE_INPUT_IN_MILLIS: Source = Source(22)
+ /**
+ * Directionless pointer acceleration with values >= 0 in distance units per second
+ * squared, where one distance unit is equal to the brush size.
+ */
+ @JvmField
+ public val ACCELERATION_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED: Source =
+ Source(23)
+ /**
+ * Signed x component of pointer acceleration in distance units per second squared,
+ * where one distance unit is equal to the brush size.
+ */
+ @JvmField
+ public val ACCELERATION_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED: Source =
+ Source(24)
+ /**
+ * Signed y component of pointer acceleration in distance units per second squared,
+ * where one distance unit is equal to the brush size.
+ */
+ @JvmField
+ public val ACCELERATION_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED: Source =
+ Source(25)
+ /**
+ * Pointer acceleration along the current direction of travel in distance units per
+ * second squared, where one distance unit is equal to the brush size. A positive value
+ * indicates that the pointer is accelerating along the current direction of travel,
+ * while a negative value indicates that the pointer is decelerating.
+ */
+ @JvmField
+ public val ACCELERATION_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED: Source =
+ Source(26)
+ /**
+ * Pointer acceleration perpendicular to the current direction of travel in distance
+ * units per second squared, where one distance unit is equal to the brush size. If the
+ * X- and Y-axes of stroke space were rotated so that the positive X-axis points in the
+ * direction of stroke travel, then a positive value for this source indicates
+ * acceleration along the positive Y-axis (and a negative value indicates acceleration
+ * along the negative Y-axis).
+ */
+ @JvmField
+ public val ACCELERATION_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED: Source =
+ Source(27)
+ /**
+ * The physical speed of the input pointer at the point in question, in centimeters per
+ * second.
+ */
+ @JvmField public val INPUT_SPEED_IN_CENTIMETERS_PER_SECOND: Source = Source(28)
+ /**
+ * Signed x component of the physical velocity of the input pointer at the point in
+ * question, in centimeters per second.
+ */
+ @JvmField public val INPUT_VELOCITY_X_IN_CENTIMETERS_PER_SECOND: Source = Source(29)
+ /**
+ * Signed y component of the physical velocity of the input pointer at the point in
+ * question, in centimeters per second.
+ */
+ @JvmField public val INPUT_VELOCITY_Y_IN_CENTIMETERS_PER_SECOND: Source = Source(30)
+ /**
+ * The physical distance traveled by the input pointer from the start of the stroke
+ * along the input path to the point in question, in centimeters.
+ */
+ @JvmField public val INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS: Source = Source(31)
+ /**
+ * The physical distance that the input pointer would have to travel from its actual
+ * last real position along its predicted path to reach the predicted point in question,
+ * in centimeters. For points on the stroke before the predicted portion, this has a
+ * value of zero.
+ */
+ @JvmField
+ public val PREDICTED_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS: Source = Source(32)
+ /**
+ * The directionless physical acceleration of the input pointer at the point in
+ * question, with values >= 0, in centimeters per second squared.
+ */
+ @JvmField
+ public val INPUT_ACCELERATION_IN_CENTIMETERS_PER_SECOND_SQUARED: Source = Source(33)
+ /**
+ * Signed x component of the physical acceleration of the input pointer, in centimeters
+ * per second squared.
+ */
+ @JvmField
+ public val INPUT_ACCELERATION_X_IN_CENTIMETERS_PER_SECOND_SQUARED: Source = Source(34)
+ /**
+ * Signed y component of the physical acceleration of the input pointer, in centimeters
+ * per second squared.
+ */
+ @JvmField
+ public val INPUT_ACCELERATION_Y_IN_CENTIMETERS_PER_SECOND_SQUARED: Source = Source(35)
+ /**
+ * The physical acceleration of the input pointer along its current direction of travel
+ * at the point in question, in centimeters per second squared. A positive value
+ * indicates that the pointer is accelerating along the current direction of travel,
+ * while a negative value indicates that the pointer is decelerating.
+ */
+ @JvmField
+ public val INPUT_ACCELERATION_FORWARD_IN_CENTIMETERS_PER_SECOND_SQUARED: Source =
+ Source(36)
+ /**
+ * The physical acceleration of the input pointer perpendicular to its current direction
+ * of travel at the point in question, in centimeters per second squared. If the X- and
+ * Y-axes of stroke space were rotated so that the positive X-axis points in the
+ * direction of stroke travel, then a positive value for this source indicates
+ * acceleration along the positive Y-axis (and a negative value indicates acceleration
+ * along the negative Y-axis).
+ */
+ @JvmField
+ public val INPUT_ACCELERATION_LATERAL_IN_CENTIMETERS_PER_SECOND_SQUARED: Source =
+ Source(37)
+ private const val PREFIX = "BrushBehavior.Source."
+ }
+ }
+
+ /** List of tip properties that can be modified by a [BrushBehavior]. */
+ public class Target private constructor(@JvmField internal val value: Int) {
+
+ public fun toSimpleString(): String =
+ when (this) {
+ WIDTH_MULTIPLIER -> "WIDTH_MULTIPLIER"
+ HEIGHT_MULTIPLIER -> "HEIGHT_MULTIPLIER"
+ SIZE_MULTIPLIER -> "SIZE_MULTIPLIER"
+ SLANT_OFFSET_IN_RADIANS -> "SLANT_OFFSET_IN_RADIANS"
+ PINCH_OFFSET -> "PINCH_OFFSET"
+ ROTATION_OFFSET_IN_RADIANS -> "ROTATION_OFFSET_IN_RADIANS"
+ CORNER_ROUNDING_OFFSET -> "CORNER_ROUNDING_OFFSET"
+ POSITION_OFFSET_X_IN_MULTIPLES_OF_BRUSH_SIZE ->
+ "POSITION_OFFSET_X_IN_MULTIPLES_OF_BRUSH_SIZE"
+ POSITION_OFFSET_Y_IN_MULTIPLES_OF_BRUSH_SIZE ->
+ "POSITION_OFFSET_Y_IN_MULTIPLES_OF_BRUSH_SIZE"
+ POSITION_OFFSET_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE ->
+ "POSITION_OFFSET_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE"
+ POSITION_OFFSET_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE ->
+ "POSITION_OFFSET_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE"
+ HUE_OFFSET_IN_RADIANS -> "HUE_OFFSET_IN_RADIANS"
+ SATURATION_MULTIPLIER -> "SATURATION_MULTIPLIER"
+ LUMINOSITY -> "LUMINOSITY"
+ OPACITY_MULTIPLIER -> "OPACITY_MULTIPLIER"
+ else -> "INVALID"
+ }
+
+ override fun toString(): String = PREFIX + this.toSimpleString()
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is Target) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+
+ /**
+ * Scales the brush-tip width, starting from the value calculated using
+ * [BrushTip.scaleX] and [BrushTip.scaleY]. The final brush width is clamped to a
+ * maximum of twice the base width. If multiple behaviors have one of these targets,
+ * they stack multiplicatively.
+ */
+ @JvmField public val WIDTH_MULTIPLIER: Target = Target(0)
+ /**
+ * Scales the brush-tip height, starting from the value calculated using
+ * [BrushTip.scaleX] and [BrushTip.scaleY]. The final brush height is clamped to a
+ * maximum of twice the base height. If multiple behaviors have one of these targets,
+ * they stack multiplicatively.
+ */
+ @JvmField public val HEIGHT_MULTIPLIER: Target = Target(1)
+ /** Convenience enumerator to target both [WIDTH_MULTIPLIER] and [HEIGHT_MULTIPLIER]. */
+ @JvmField public val SIZE_MULTIPLIER: Target = Target(2)
+ /**
+ * Adds the target modifier to [BrushTip.slant]. The final brush slant value is clamped
+ * to [-π/2, π/2]. If multiple behaviors have this target, they stack additively.
+ */
+ @JvmField public val SLANT_OFFSET_IN_RADIANS: Target = Target(3)
+ /**
+ * Adds the target modifier to [BrushTip.pinch]. The final brush pinch value is clamped
+ * to [0, 1]. If multiple behaviors have this target, they stack additively.
+ */
+ @JvmField public val PINCH_OFFSET: Target = Target(4)
+ /**
+ * Adds the target modifier to [BrushTip.rotation]. The final brush rotation angle is
+ * effectively normalized (mod 2π). If multiple behaviors have this target, they stack
+ * additively.
+ */
+ @JvmField public val ROTATION_OFFSET_IN_RADIANS: Target = Target(5)
+ /**
+ * Adds the target modifier to [BrushTip.cornerRounding]. The final brush corner
+ * rounding value is clamped to [0, 1]. If multiple behaviors have this target, they
+ * stack additively.
+ */
+ @JvmField public val CORNER_ROUNDING_OFFSET: Target = Target(6)
+ /**
+ * Adds the target modifier to the brush tip x position, where one distance unit is
+ * equal to the brush size.
+ */
+ @JvmField public val POSITION_OFFSET_X_IN_MULTIPLES_OF_BRUSH_SIZE: Target = Target(7)
+ /**
+ * Adds the target modifier to the brush tip y position, where one distance unit is
+ * equal to the brush size.
+ */
+ @JvmField public val POSITION_OFFSET_Y_IN_MULTIPLES_OF_BRUSH_SIZE: Target = Target(8)
+ /**
+ * Moves the brush tip center forward (or backward, for negative values) from the input
+ * position, in the current direction of stroke travel, where one distance unit is equal
+ * to the brush size.
+ */
+ @JvmField
+ public val POSITION_OFFSET_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE: Target = Target(9)
+ /**
+ * Moves the brush tip center sideways from the input position, relative to the
+ * direction of stroke travel, where one distance unit is equal to the brush size. If
+ * the X- and Y-axes of stroke space were rotated so that the positive X-axis points in
+ * the direction of stroke travel, then a positive value for this offset moves the brush
+ * tip center towards the positive Y-axis (and a negative value moves the brush tip
+ * center towards the negative Y-axis).
+ */
+ @JvmField
+ public val POSITION_OFFSET_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE: Target = Target(10)
+
+ // The following are targets for tip color adjustments, including opacity. Renderers can
+ // apply
+ // them to the brush color when a stroke is drawn to contribute to the local color of
+ // each
+ // part of the stroke.
+ /**
+ * Shifts the hue of the base brush color. A positive offset shifts around the hue wheel
+ * from red towards orange, while a negative offset shifts the other way, from red
+ * towards violet. The final hue offset is not clamped, but is effectively normalized
+ * (mod 2π). If multiple behaviors have this target, they stack additively.
+ */
+ @JvmField public val HUE_OFFSET_IN_RADIANS: Target = Target(11)
+ /**
+ * Scales the saturation of the base brush color. If multiple behaviors have one of
+ * these targets, they stack multiplicatively. The final saturation multiplier is
+ * clamped to [0, 2].
+ */
+ @JvmField public val SATURATION_MULTIPLIER: Target = Target(12)
+ /**
+ * Target the luminosity of the color. An offset of +/-100% corresponds to changing the
+ * luminosity by up to +/-100%.
+ */
+ @JvmField public val LUMINOSITY: Target = Target(13)
+ /**
+ * Scales the opacity of the base brush color. If multiple behaviors have one of these
+ * targets, they stack multiplicatively. The final opacity multiplier is clamped to
+ * [0, 2].
+ */
+ @JvmField public val OPACITY_MULTIPLIER: Target = Target(14)
+
+ private const val PREFIX = "BrushBehavior.Target."
+ }
+ }
+
+ /**
+ * The desired behavior when an input value is outside the range defined by
+ * [sourceValueRangeLowerBound, sourceValueRangeUpperBound].
+ */
+ public class OutOfRange private constructor(@JvmField internal val value: Int) {
+ public fun toSimpleString(): String =
+ when (this) {
+ CLAMP -> "CLAMP"
+ REPEAT -> "REPEAT"
+ MIRROR -> "MIRROR"
+ else -> "INVALID"
+ }
+
+ override fun toString(): String = PREFIX + this.toSimpleString()
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is OutOfRange) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+
+ // Values outside the range will be clamped to not exceed the bounds.
+ @JvmField public val CLAMP: OutOfRange = OutOfRange(0)
+ // Values will be shifted by an integer multiple of the range size so that they fall
+ // within
+ // the bounds.
+ //
+ // In this case, the range will be treated as a half-open interval, with a value exactly
+ // at
+ // [sourceValueRangeUpperBound] being treated as though it was
+ // [sourceValueRangeLowerBound].
+ @JvmField public val REPEAT: OutOfRange = OutOfRange(1)
+ // Similar to [Repeat], but every other repetition of the bounds will be mirrored, as
+ // though
+ // the
+ // two elements [sourceValueRangeLowerBound] and [sourceValueRangeUpperBound] were
+ // swapped.
+ // This means the range does not need to be treated as a half-open interval like in the
+ // case
+ // of [Repeat].
+ @JvmField public val MIRROR: OutOfRange = OutOfRange(2)
+ private const val PREFIX = "BrushBehavior.OutOfRange."
+ }
+ }
+
+ /** List of input properties that might not be reported by inputs. */
+ public class OptionalInputProperty private constructor(@JvmField internal val value: Int) {
+
+ public fun toSimpleString(): String =
+ when (this) {
+ PRESSURE -> "PRESSURE"
+ TILT -> "TILT"
+ ORIENTATION -> "ORIENTATION"
+ TILT_X_AND_Y -> "TILT_X_AND_Y"
+ else -> "INVALID"
+ }
+
+ override fun toString(): String = PREFIX + this.toSimpleString()
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is OptionalInputProperty) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+
+ @JvmField public val PRESSURE: OptionalInputProperty = OptionalInputProperty(0)
+ @JvmField public val TILT: OptionalInputProperty = OptionalInputProperty(1)
+ @JvmField public val ORIENTATION: OptionalInputProperty = OptionalInputProperty(2)
+ /** Tilt-x and tilt-y require both tilt and orientation to be reported. */
+ @JvmField public val TILT_X_AND_Y: OptionalInputProperty = OptionalInputProperty(3)
+ private const val PREFIX = "BrushBehavior.OptionalInputProperty."
+ }
+ }
+
+ /** A binary operation for combining two values in a [BinaryOpNode]. */
+ public class BinaryOp private constructor(@JvmField internal val value: Int) {
+
+ internal fun toSimpleString(): String =
+ when (this) {
+ PRODUCT -> "PRODUCT"
+ SUM -> "SUM"
+ else -> "INVALID"
+ }
+
+ override fun toString(): String = PREFIX + this.toSimpleString()
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is BinaryOp) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+ /** Evaluates to the product of the two input values, or null if either is null. */
+ @JvmField public val PRODUCT: BinaryOp = BinaryOp(0)
+ /** Evaluates to the sum of the two input values, or null if either is null. */
+ @JvmField public val SUM: BinaryOp = BinaryOp(1)
+
+ private const val PREFIX = "BrushBehavior.BinaryOp."
+ }
+ }
+
+ /** Dimensions/units for measuring the [dampingGap] field of a [DampingNode] */
+ public class DampingSource private constructor(@JvmField internal val value: Int) {
+
+ internal fun toSimpleString(): String =
+ when (this) {
+ TIME_IN_SECONDS -> "TIME_IN_SECONDS"
+ else -> "INVALID"
+ }
+
+ override fun toString(): String = PREFIX + this.toSimpleString()
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is DampingSource) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+ /** Value damping occurs over time, and the [dampingGap] is measured in seconds. */
+ @JvmField public val TIME_IN_SECONDS: DampingSource = DampingSource(0)
+
+ private const val PREFIX = "BrushBehavior.DampingSource."
+ }
+ }
+
+ /**
+ * Represents one node in a [BrushBehavior]'s expression graph. [Node] objects are immutable and
+ * their inputs must be chosen at construction time; therefore, they can only ever be assembled
+ * into an acyclic graph.
+ */
+ public abstract class Node internal constructor() {
+ /** Returns the ordered list of inputs that this node directly depends on. */
+ public open fun inputs(): List<ValueNode> = emptyList()
+
+ /** Appends a native version of this [Node] to a native [BrushBehavior]. */
+ internal abstract fun appendToNativeBrushBehavior(nativeBehaviorPointer: Long)
+ }
+
+ /**
+ * A [ValueNode] is a non-terminal node in the graph; it produces a value to be consumed as an
+ * input by other [Node]s, and may itself depend on zero or more inputs.
+ */
+ public abstract class ValueNode internal constructor() : Node() {}
+
+ /** A [ValueNode] that gets data from the stroke input batch. */
+ public class SourceNode
+ @JvmOverloads
+ constructor(
+ public val source: Source,
+ public val sourceValueRangeLowerBound: Float,
+ public val sourceValueRangeUpperBound: Float,
+ public val sourceOutOfRangeBehavior: OutOfRange = OutOfRange.CLAMP,
+ ) : ValueNode() {
+ init {
+ require(sourceValueRangeLowerBound.isFinite()) {
+ "sourceValueRangeLowerBound must be finite, was $sourceValueRangeLowerBound"
+ }
+ require(sourceValueRangeUpperBound.isFinite()) {
+ "sourceValueRangeUpperBound must be finite, was $sourceValueRangeUpperBound"
+ }
+ require(sourceValueRangeLowerBound != sourceValueRangeUpperBound) {
+ "sourceValueRangeLowerBound and sourceValueRangeUpperBound must be distinct, both were $sourceValueRangeLowerBound"
+ }
+ }
+
+ override fun appendToNativeBrushBehavior(nativeBehaviorPointer: Long) {
+ nativeAppendSourceNode(
+ nativeBehaviorPointer,
+ source.value,
+ sourceValueRangeLowerBound,
+ sourceValueRangeUpperBound,
+ sourceOutOfRangeBehavior.value,
+ )
+ }
+
+ override fun toString(): String =
+ "SourceNode(${source.toSimpleString()}, $sourceValueRangeLowerBound, $sourceValueRangeUpperBound, ${sourceOutOfRangeBehavior.toSimpleString()})"
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is SourceNode) return false
+ return source == other.source &&
+ sourceValueRangeLowerBound == other.sourceValueRangeLowerBound &&
+ sourceValueRangeUpperBound == other.sourceValueRangeUpperBound &&
+ sourceOutOfRangeBehavior == other.sourceOutOfRangeBehavior
+ }
+
+ override fun hashCode(): Int {
+ var result = source.hashCode()
+ result = 31 * result + sourceValueRangeLowerBound.hashCode()
+ result = 31 * result + sourceValueRangeUpperBound.hashCode()
+ result = 31 * result + sourceOutOfRangeBehavior.hashCode()
+ return result
+ }
+
+ /** Appends a native `BrushBehavior::SourceNode` to a native brush behavior struct. */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeAppendSourceNode(
+ nativeBehaviorPointer: Long,
+ source: Int,
+ sourceValueRangeLowerBound: Float,
+ sourceValueRangeUpperBound: Float,
+ sourceOutOfRangeBehavior: Int,
+ )
+ }
+
+ /** A [ValueNode] that produces a constant output value. */
+ public class ConstantNode constructor(public val value: Float) : ValueNode() {
+ init {
+ require(value.isFinite()) { "value must be finite, was $value" }
+ }
+
+ override fun appendToNativeBrushBehavior(nativeBehaviorPointer: Long) {
+ nativeAppendConstantNode(nativeBehaviorPointer, value)
+ }
+
+ override fun toString(): String = "ConstantNode($value)"
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is ConstantNode) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ /** Appends a native `BrushBehavior::ConstantNode` to a native brush behavior struct. */
+ private external fun nativeAppendConstantNode(
+ nativeBehaviorPointer: Long,
+ value: Float
+ ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ }
+
+ /**
+ * A [ValueNode] for filtering out a branch of a behavior graph unless a particular stroke input
+ * property is missing.
+ */
+ public class FallbackFilterNode
+ constructor(public val isFallbackFor: OptionalInputProperty, public val input: ValueNode) :
+ ValueNode() {
+ override fun inputs(): List<ValueNode> = listOf(input)
+
+ override fun appendToNativeBrushBehavior(nativeBehaviorPointer: Long) {
+ nativeAppendFallbackFilterNode(nativeBehaviorPointer, isFallbackFor.value)
+ }
+
+ override fun toString(): String =
+ "FallbackFilterNode(${isFallbackFor.toSimpleString()}, $input)"
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is FallbackFilterNode) return false
+ return isFallbackFor == other.isFallbackFor && input == other.input
+ }
+
+ override fun hashCode(): Int {
+ var result = isFallbackFor.hashCode()
+ result = 31 * result + input.hashCode()
+ return result
+ }
+
+ /**
+ * Appends a native `BrushBehavior::FallbackFilterNode` to a native brush behavior struct.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeAppendFallbackFilterNode(
+ nativeBehaviorPointer: Long,
+ isFallbackFor: Int,
+ )
+ }
+
+ /**
+ * A [ValueNode] for filtering out a branch of a behavior graph unless this stroke's tool type
+ * is in the specified set.
+ */
+ public class ToolTypeFilterNode
+ constructor(
+ // The [enabledToolTypes] val below is a defensive copy of this parameter.
+ enabledToolTypes: Set<InputToolType>,
+ public val input: ValueNode,
+ ) : ValueNode() {
+ public val enabledToolTypes: Set<InputToolType> = unmodifiableSet(enabledToolTypes.toSet())
+
+ init {
+ require(!enabledToolTypes.isEmpty()) { "enabledToolTypes must be non-empty" }
+ }
+
+ override fun inputs(): List<ValueNode> = listOf(input)
+
+ override fun appendToNativeBrushBehavior(nativeBehaviorPointer: Long) {
+ nativeAppendToolTypeFilterNode(
+ nativeBehaviorPointer = nativeBehaviorPointer,
+ mouseEnabled = enabledToolTypes.contains(InputToolType.MOUSE),
+ touchEnabled = enabledToolTypes.contains(InputToolType.TOUCH),
+ stylusEnabled = enabledToolTypes.contains(InputToolType.STYLUS),
+ unknownEnabled = enabledToolTypes.contains(InputToolType.UNKNOWN),
+ )
+ }
+
+ override fun toString(): String = "ToolTypeFilterNode($enabledToolTypes, $input)"
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is ToolTypeFilterNode) return false
+ return enabledToolTypes == other.enabledToolTypes && input == other.input
+ }
+
+ override fun hashCode(): Int {
+ var result = enabledToolTypes.hashCode()
+ result = 31 * result + input.hashCode()
+ return result
+ }
+
+ /**
+ * Appends a native `BrushBehavior::ToolTypeFilterNode` to a native brush behavior struct.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeAppendToolTypeFilterNode(
+ nativeBehaviorPointer: Long,
+ mouseEnabled: Boolean,
+ touchEnabled: Boolean,
+ stylusEnabled: Boolean,
+ unknownEnabled: Boolean,
+ )
+ }
+
+ /**
+ * A [ValueNode] that damps changes in an input value, causing the output value to slowly follow
+ * changes in the input value over a specified time or distance.
+ */
+ public class DampingNode
+ constructor(
+ public val dampingSource: DampingSource,
+ public val dampingGap: Float,
+ public val input: ValueNode,
+ ) : ValueNode() {
+ init {
+ require(dampingGap.isFinite() && dampingGap >= 0.0f) {
+ "dampingGap must be finite and non-negative, was $dampingGap"
+ }
+ }
+
+ override fun inputs(): List<ValueNode> = listOf(input)
+
+ override fun appendToNativeBrushBehavior(nativeBehaviorPointer: Long) {
+ nativeAppendDampingNode(nativeBehaviorPointer, dampingSource.value, dampingGap)
+ }
+
+ override fun toString(): String =
+ "DampingNode(${dampingSource.toSimpleString()}, $dampingGap, $input)"
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is DampingNode) return false
+ return dampingSource == other.dampingSource &&
+ dampingGap == other.dampingGap &&
+ input == other.input
+ }
+
+ override fun hashCode(): Int {
+ var result = dampingSource.hashCode()
+ result = 31 * result + dampingGap.hashCode()
+ result = 31 * result + input.hashCode()
+ return result
+ }
+
+ /** Appends a native `BrushBehavior::DampingNode` to a native brush behavior struct. */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeAppendDampingNode(
+ nativeBehaviorPointer: Long,
+ dampingSource: Int,
+ dampingGap: Float,
+ )
+ }
+
+ /** A [ValueNode] that maps an input value through a response curve. */
+ public class ResponseNode
+ constructor(public val responseCurve: EasingFunction, public val input: ValueNode) :
+ ValueNode() {
+ override fun inputs(): List<ValueNode> = listOf(input)
+
+ override fun appendToNativeBrushBehavior(nativeBehaviorPointer: Long) {
+ when (responseCurve) {
+ is EasingFunction.Predefined ->
+ nativeAppendResponseNodePredefined(nativeBehaviorPointer, responseCurve.value)
+ is EasingFunction.CubicBezier ->
+ nativeAppendResponseNodeCubicBezier(
+ nativeBehaviorPointer,
+ responseCurve.x1,
+ responseCurve.y1,
+ responseCurve.x2,
+ responseCurve.y2,
+ )
+ is EasingFunction.Steps ->
+ nativeAppendResponseNodeSteps(
+ nativeBehaviorPointer,
+ responseCurve.stepCount,
+ responseCurve.stepPosition.value,
+ )
+ is EasingFunction.Linear ->
+ nativeAppendResponseNodeLinear(
+ nativeBehaviorPointer,
+ FloatArray(responseCurve.points.size * 2).apply {
+ var index = 0
+ for (point in responseCurve.points) {
+ set(index, point.x)
+ ++index
+ set(index, point.y)
+ ++index
+ }
+ },
+ )
+ }
+ }
+
+ override fun toString(): String = "ResponseNode($responseCurve, $input)"
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is ResponseNode) return false
+ return responseCurve == other.responseCurve && input == other.input
+ }
+
+ override fun hashCode(): Int {
+ var result = responseCurve.hashCode()
+ result = 31 * result + input.hashCode()
+ return result
+ }
+
+ /**
+ * Appends a native `BrushBehavior::ResponseNode` with response curve of type
+ * [EasingFunction.Predefined] to a native brush behavior struct.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeAppendResponseNodePredefined(
+ nativeBehaviorPointer: Long,
+ predefinedResponseCurve: Int,
+ )
+
+ /**
+ * Appends a native `BrushBehavior::ResponseNode` with response curve of type
+ * [EasingFunction.CubicBezier] to a native brush behavior struct.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeAppendResponseNodeCubicBezier(
+ nativeBehaviorPointer: Long,
+ cubicBezierX1: Float,
+ cubicBezierX2: Float,
+ cubicBezierY1: Float,
+ cubicBezierY2: Float,
+ )
+
+ /**
+ * Appends a native `BrushBehavior::ResponseNode` with response curve of type
+ * [EasingFunction.Steps] to a native brush behavior struct.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeAppendResponseNodeSteps(
+ nativeBehaviorPointer: Long,
+ stepsCount: Int,
+ stepsPosition: Int,
+ )
+
+ /**
+ * Appends a native `BrushBehavior::ResponseNode` with response curve of type
+ * [EasingFunction.Linear] to a native brush behavior struct.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeAppendResponseNodeLinear(
+ nativeBehaviorPointer: Long,
+ points: FloatArray,
+ )
+ }
+
+ /** A [ValueNode] that combines two other values with a binary operation. */
+ public class BinaryOpNode
+ constructor(
+ public val operation: BinaryOp,
+ public val firstInput: ValueNode,
+ public val secondInput: ValueNode,
+ ) : ValueNode() {
+ override fun inputs(): List<ValueNode> = listOf(firstInput, secondInput)
+
+ override fun appendToNativeBrushBehavior(nativeBehaviorPointer: Long) {
+ nativeAppendBinaryOpNode(nativeBehaviorPointer, operation.value)
+ }
+
+ override fun toString(): String =
+ "BinaryOpNode(${operation.toSimpleString()}, $firstInput, $secondInput)"
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is BinaryOpNode) return false
+ return operation == other.operation &&
+ firstInput == other.firstInput &&
+ secondInput == other.secondInput
+ }
+
+ override fun hashCode(): Int {
+ var result = operation.hashCode()
+ result = 31 * result + firstInput.hashCode()
+ result = 31 * result + secondInput.hashCode()
+ return result
+ }
+
+ /** Appends a native `BrushBehavior::BinaryOpNode` to a native brush behavior struct. */
+ private external fun nativeAppendBinaryOpNode(
+ nativeBehaviorPointer: Long,
+ operation: Int
+ ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ }
+
+ /**
+ * A [TargetNode] is a terminal node in the graph; it does not produce a value and cannot be
+ * used as an input to other [Node]s, but instead applies a modification to the brush tip state.
+ * A [BrushBehavior] consists of a list of [TargetNode]s and the various [ValueNode]s that they
+ * transitively depend on.
+ */
+ public class TargetNode
+ constructor(
+ public val target: Target,
+ public val targetModifierRangeLowerBound: Float,
+ public val targetModifierRangeUpperBound: Float,
+ public val input: ValueNode,
+ ) : Node() {
+ init {
+ require(targetModifierRangeLowerBound.isFinite()) {
+ "targetModifierRangeLowerBound must be finite, was $targetModifierRangeLowerBound"
+ }
+ require(targetModifierRangeUpperBound.isFinite()) {
+ "targetModifierRangeUpperBound must be finite, was $targetModifierRangeUpperBound"
+ }
+ require(targetModifierRangeLowerBound != targetModifierRangeUpperBound) {
+ "targetModifierRangeLowerBound and targetModifierRangeUpperBound must be distinct, both were $targetModifierRangeLowerBound"
+ }
+ }
+
+ override fun inputs(): List<ValueNode> = listOf(input)
+
+ override fun appendToNativeBrushBehavior(nativeBehaviorPointer: Long) {
+ nativeAppendTargetNode(
+ nativeBehaviorPointer,
+ target.value,
+ targetModifierRangeLowerBound,
+ targetModifierRangeUpperBound,
+ )
+ }
+
+ override fun toString(): String =
+ "TargetNode(${target.toSimpleString()}, $targetModifierRangeLowerBound, $targetModifierRangeUpperBound, $input)"
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is TargetNode) return false
+ return target == other.target &&
+ targetModifierRangeLowerBound == other.targetModifierRangeLowerBound &&
+ targetModifierRangeUpperBound == other.targetModifierRangeUpperBound &&
+ input == other.input
+ }
+
+ override fun hashCode(): Int {
+ var result = target.hashCode()
+ result = 31 * result + targetModifierRangeLowerBound.hashCode()
+ result = 31 * result + targetModifierRangeUpperBound.hashCode()
+ result = 31 * result + input.hashCode()
+ return result
+ }
+
+ /** Appends a native `BrushBehavior::TargetNode` to a native brush behavior struct. */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeAppendTargetNode(
+ nativeBehaviorPointer: Long,
+ target: Int,
+ targetModifierRangeLowerBound: Float,
+ targetModifierRangeUpperBound: Float,
+ )
+ }
+}
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushCoat.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushCoat.kt
new file mode 100644
index 0000000..1582b57
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushCoat.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.annotation.RestrictTo
+import androidx.ink.nativeloader.NativeLoader
+import java.util.Collections.unmodifiableList
+import kotlin.jvm.JvmOverloads
+import kotlin.jvm.JvmStatic
+
+/**
+ * A [BrushCoat] represents one coat of paint applied by a brush. It includes a single [BrushPaint],
+ * as well as one or more [BrushTip]s used to apply that paint. Multiple [BrushCoat] can be combined
+ * within a single brush; when a stroke drawn by a multi-coat brush is rendered, each coat of paint
+ * will be drawn entirely atop the previous coat, even if the stroke crosses over itself, as though
+ * each coat were painted in its entirety one at a time.
+ */
+@ExperimentalInkCustomBrushApi
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+@Suppress("NotCloseable") // Finalize is only used to free the native peer.
+public class BrushCoat
+@JvmOverloads
+constructor(
+ // The [tips] val below is a defensive copy of this parameter.
+ tips: List<BrushTip>,
+ /** The paint to be applied in this coat. */
+ public val paint: BrushPaint = BrushPaint(),
+) {
+
+ /**
+ * The tip(s) used to apply the paint.
+ *
+ * For now, there must be exactly one tip. This restriction is expected to be lifted in a future
+ * release.
+ */
+ // TODO: b/285594469 - More than one tip.
+ public val tips: List<BrushTip> = unmodifiableList(tips.toList())
+
+ @JvmOverloads
+ public constructor(
+ tip: BrushTip = BrushTip(),
+ paint: BrushPaint = BrushPaint(),
+ ) : this(listOf(tip), paint)
+
+ /** A handle to the underlying native [BrushCoat] object. */
+ internal val nativePointer: Long =
+ nativeCreateBrushCoat(tips.map { it.nativePointer }.toLongArray(), paint.nativePointer)
+
+ /**
+ * Creates a copy of `this` and allows named properties to be altered while keeping the rest
+ * unchanged.
+ */
+ @JvmSynthetic
+ public fun copy(tips: List<BrushTip> = this.tips, paint: BrushPaint = this.paint): BrushCoat {
+ return if (tips == this.tips && paint == this.paint) {
+ this
+ } else {
+ BrushCoat(tips, paint)
+ }
+ }
+
+ /**
+ * Creates a copy of `this` and allows named properties to be altered while keeping the rest
+ * unchanged.
+ */
+ @JvmSynthetic
+ public fun copy(tip: BrushTip, paint: BrushPaint = this.paint): BrushCoat {
+ return if (this.tips.size == 1 && tip == this.tips[0] && paint == this.paint) {
+ this
+ } else {
+ BrushCoat(tip, paint)
+ }
+ }
+
+ /**
+ * Returns a [Builder] with values set equivalent to `this`. Java developers, use the returned
+ * builder to build a copy of a BrushCoat.
+ */
+ public fun toBuilder(): Builder = Builder().setTips(tips).setPaint(paint)
+
+ /**
+ * Builder for [BrushCoat].
+ *
+ * For Java developers, use BrushCoat.Builder to construct [BrushCoat] with default values,
+ * overriding only as needed. For example: `BrushCoat family = new
+ * BrushCoat.Builder().tip(presetBrushTip).build();`
+ */
+ public class Builder {
+ private var tips: List<BrushTip> = listOf(BrushTip())
+ private var paint: BrushPaint = BrushPaint()
+
+ public fun setTip(tip: BrushTip): Builder {
+ this.tips = listOf(tip)
+ return this
+ }
+
+ public fun setTips(tips: List<BrushTip>): Builder {
+ this.tips = tips.toList()
+ return this
+ }
+
+ public fun setPaint(paint: BrushPaint): Builder {
+ this.paint = paint
+ return this
+ }
+
+ public fun build(): BrushCoat = BrushCoat(tips, paint)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is BrushCoat) return false
+ return tips == other.tips && paint == other.paint
+ }
+
+ override fun hashCode(): Int {
+ var result = tips.hashCode()
+ result = 31 * result + paint.hashCode()
+ return result
+ }
+
+ override fun toString(): String = "BrushCoat(tips=$tips, paint=$paint)"
+
+ /** Deletes native BrushCoat memory. */
+ protected fun finalize() {
+ // NOMUTANTS -- Not tested post garbage collection.
+ nativeFreeBrushCoat(nativePointer)
+ }
+
+ /** Create underlying native object and return reference for all subsequent native calls. */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeCreateBrushCoat(
+ tipNativePointers: LongArray,
+ paintNativePointer: Long,
+ ): Long
+
+ /** Release the underlying memory allocated in [nativeCreateBrushCoat]. */
+ private external fun nativeFreeBrushCoat(
+ nativePointer: Long
+ ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ // Companion object gets initialized before anything else.
+ public companion object {
+ init {
+ NativeLoader.load()
+ }
+
+ /** Returns a new [BrushCoat.Builder]. */
+ @JvmStatic public fun builder(): Builder = Builder()
+ }
+}
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushFamily.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushFamily.kt
new file mode 100644
index 0000000..a0ef9e6
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushFamily.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.annotation.RestrictTo
+import androidx.ink.nativeloader.NativeLoader
+import java.util.Collections.unmodifiableList
+import kotlin.jvm.JvmOverloads
+import kotlin.jvm.JvmStatic
+
+/**
+ * A [BrushFamily] describes a family of brushes (e.g. “highlighter” or “pressure pen”),
+ * irrespective of their size or color.
+ *
+ * For now, [BrushFamily] is an opaque type that can only be instantiated via [StockBrushes]. A
+ * future version of this module will allow creating fully custom [BrushFamily] objects.
+ *
+ * [BrushFamily] objects are immutable.
+ */
+@OptIn(ExperimentalInkCustomBrushApi::class)
+@Suppress("NotCloseable") // Finalize is only used to free the native peer.
+public class BrushFamily
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+@ExperimentalInkCustomBrushApi
+@JvmOverloads
+constructor(
+ // The [coats] val below is a defensive copy of this parameter.
+ coats: List<BrushCoat>,
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public val uri: String? = null,
+) {
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public val coats: List<BrushCoat> = unmodifiableList(coats.toList())
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ @ExperimentalInkCustomBrushApi
+ @JvmOverloads
+ public constructor(
+ tip: BrushTip = BrushTip(),
+ paint: BrushPaint = BrushPaint(),
+ uri: String? = null,
+ ) : this(listOf(BrushCoat(tip, paint)), uri)
+
+ /** A handle to the underlying native [BrushFamily] object. */
+ internal val nativePointer: Long =
+ nativeCreateBrushFamily(coats.map { it.nativePointer }.toLongArray(), uri)
+
+ /**
+ * Creates a copy of `this` and allows named properties to be altered while keeping the rest
+ * unchanged.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ @ExperimentalInkCustomBrushApi
+ @JvmSynthetic
+ public fun copy(coats: List<BrushCoat> = this.coats, uri: String? = this.uri): BrushFamily {
+ return if (coats == this.coats && uri == this.uri) {
+ this
+ } else {
+ BrushFamily(coats, uri)
+ }
+ }
+
+ /**
+ * Creates a copy of `this` and allows named properties to be altered while keeping the rest
+ * unchanged.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ @ExperimentalInkCustomBrushApi
+ @JvmSynthetic
+ public fun copy(coat: BrushCoat, uri: String? = this.uri): BrushFamily {
+ return copy(coats = listOf(coat), uri = uri)
+ }
+
+ /**
+ * Creates a copy of `this` and allows named properties to be altered while keeping the rest
+ * unchanged.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ @ExperimentalInkCustomBrushApi
+ @JvmSynthetic
+ public fun copy(tip: BrushTip, paint: BrushPaint, uri: String? = this.uri): BrushFamily {
+ return copy(coat = BrushCoat(tip, paint), uri = uri)
+ }
+
+ /**
+ * Returns a [Builder] with values set equivalent to `this`. Java developers, use the returned
+ * builder to build a copy of a BrushFamily.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ @ExperimentalInkCustomBrushApi
+ public fun toBuilder(): Builder = Builder().setCoats(coats).setUri(uri)
+
+ /**
+ * Builder for [BrushFamily].
+ *
+ * For Java developers, use BrushFamily.Builder to construct [BrushFamily] with default values,
+ * overriding only as needed. For example: `BrushFamily family = new
+ * BrushFamily.Builder().coat(presetBrushCoat).build();`
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ @ExperimentalInkCustomBrushApi
+ public class Builder {
+ private var coats: List<BrushCoat> = listOf(BrushCoat(BrushTip(), BrushPaint()))
+ private var uri: String? = null
+
+ public fun setCoat(tip: BrushTip, paint: BrushPaint): Builder =
+ setCoat(BrushCoat(tip, paint))
+
+ public fun setCoat(coat: BrushCoat): Builder = setCoats(listOf(coat))
+
+ public fun setCoats(coats: List<BrushCoat>): Builder {
+ this.coats = coats.toList()
+ return this
+ }
+
+ public fun setUri(uri: String?): Builder {
+ this.uri = uri
+ return this
+ }
+
+ public fun build(): BrushFamily = BrushFamily(coats, uri)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is BrushFamily) return false
+ return coats == other.coats && uri == other.uri
+ }
+
+ override fun hashCode(): Int {
+ var result = coats.hashCode()
+ result = 31 * result + uri.hashCode()
+ return result
+ }
+
+ override fun toString(): String = "BrushFamily(coats=$coats, uri=$uri)"
+
+ /** Deletes native BrushFamily memory. */
+ protected fun finalize() {
+ // NOMUTANTS -- Not tested post garbage collection.
+ nativeFreeBrushFamily(nativePointer)
+ }
+
+ /** Create underlying native object and return reference for all subsequent native calls. */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeCreateBrushFamily(coatNativePointers: LongArray, uri: String?): Long
+
+ /** Release the underlying memory allocated in [nativeCreateBrushFamily]. */
+ private external fun nativeFreeBrushFamily(
+ nativePointer: Long
+ ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ // Companion object gets initialized before anything else.
+ public companion object {
+ init {
+ NativeLoader.load()
+ }
+
+ /** Returns a new [BrushFamily.Builder]. */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ @ExperimentalInkCustomBrushApi
+ @JvmStatic
+ public fun builder(): Builder = Builder()
+ }
+}
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushPaint.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushPaint.kt
new file mode 100644
index 0000000..002412f
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushPaint.kt
@@ -0,0 +1,666 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.annotation.FloatRange
+import androidx.annotation.RestrictTo
+import androidx.ink.geometry.AngleRadiansFloat
+import androidx.ink.nativeloader.NativeLoader
+import java.util.Collections.unmodifiableList
+import kotlin.Suppress
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmSynthetic
+
+/**
+ * Parameters that control stroke mesh rendering. Note: This contains only a subset of the
+ * parameters as support is added for them.
+ *
+ * The core of each paint consists of one or more texture layers. The output of each layer is
+ * blended together in sequence, then the combined texture is blended with the output from the brush
+ * color.
+ * - Starting with the first [TextureLayer], the combined texture for layers 0 to i (source) is
+ * blended with layer i+1 (destination) using the blend mode for layer i.
+ * - The final combined texture (source) is blended with the (possibly adjusted per-vertex) brush
+ * color (destination) according to the blend mode of the last texture layer.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+@ExperimentalInkCustomBrushApi
+@Suppress("NotCloseable") // Finalize is only used to free the native peer.
+public class BrushPaint(
+ // The [textureLayers] val below is a defensive copy of this parameter.
+ textureLayers: List<TextureLayer> = emptyList()
+) {
+ /** The textures to apply to the stroke. */
+ public val textureLayers: List<TextureLayer> = unmodifiableList(textureLayers.toList())
+
+ /** A handle to the underlying native [BrushPaint] object. */
+ internal val nativePointer: Long = nativeCreateBrushPaint(textureLayers.size)
+
+ init {
+ for (layer in textureLayers) {
+ nativeAppendTextureLayer(nativePointer, layer.nativePointer)
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is BrushPaint) return false
+ return textureLayers == other.textureLayers
+ }
+
+ override fun toString(): String = "BrushPaint(textureLayers=$textureLayers)"
+
+ override fun hashCode(): Int {
+ return textureLayers.hashCode()
+ }
+
+ /** Delete native BrushPaint memory. */
+ protected fun finalize() {
+ // NOMUTANTS -- Not tested post garbage collection.
+ nativeFreeBrushPaint(nativePointer)
+ }
+
+ /** Create underlying native object and return reference for all subsequent native calls. */
+ private external fun nativeCreateBrushPaint(
+ textureLayersCount: Int
+ ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ /**
+ * Appends a texture layer to a *mutable* C++ BrushPaint object as referenced by
+ * [nativePointer]. Only call during `init{}` so to keep this BrushPaint object immutable after
+ * construction and equivalent across Kotlin and C++.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeAppendTextureLayer(nativePointer: Long, textureLayerPointer: Long)
+
+ /** Release the underlying memory allocated in [nativeCreateBrushPaint]. */
+ private external fun nativeFreeBrushPaint(
+ nativePointer: Long
+ ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ /** Specification of how the texture should apply to the stroke. */
+ public class TextureMapping private constructor(@JvmField internal val value: Int) {
+ override fun toString(): String =
+ when (this) {
+ TILING -> "BrushPaint.TextureMapping.TILING"
+ WINDING -> "BrushPaint.TextureMapping.WINDING"
+ else -> "BrushPaint.TextureMapping.INVALID($value)"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other === this) return true
+ if (other !is TextureMapping) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+ /**
+ * The texture will repeat according to a 2D affine transformation of vertex positions.
+ * Each copy of the texture will have the same size and shape modulo reflections.
+ */
+ @JvmField public val TILING: TextureMapping = TextureMapping(0)
+ /**
+ * The texture will morph to "wind along the path of the stroke." The horizontal axis of
+ * texture space will lie along the width of the stroke and the vertical axis will lie
+ * along the direction of travel of the stroke at each point.
+ */
+ @JvmField public val WINDING: TextureMapping = TextureMapping(1)
+ }
+ }
+
+ /** Specification of the origin point to use for the texture. */
+ public class TextureOrigin private constructor(@JvmField internal val value: Int) {
+ override fun toString(): String =
+ when (this) {
+ STROKE_SPACE_ORIGIN -> "BrushPaint.TextureOrigin.STROKE_SPACE_ORIGIN"
+ FIRST_STROKE_INPUT -> "BrushPaint.TextureOrigin.FIRST_STROKE_INPUT"
+ LAST_STROKE_INPUT -> "BrushPaint.TextureOrigin.LAST_STROKE_INPUT"
+ else -> "BrushPaint.TextureOrigin.INVALID($value)"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other === this) return true
+ if (other !is TextureOrigin) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+ /**
+ * The texture origin is the origin of stroke space, however that happens to be defined
+ * for a given stroke.
+ */
+ @JvmField public val STROKE_SPACE_ORIGIN: TextureOrigin = TextureOrigin(0)
+ /** The texture origin is the first input position for the stroke. */
+ @JvmField public val FIRST_STROKE_INPUT: TextureOrigin = TextureOrigin(1)
+ /**
+ * The texture origin is the last input position (including predicted inputs) for the
+ * stroke. Note that this means that the texture origin for an in-progress stroke will
+ * move as more inputs are added.
+ */
+ @JvmField public val LAST_STROKE_INPUT: TextureOrigin = TextureOrigin(2)
+ }
+ }
+
+ /** Units for specifying [TextureLayer.sizeX] and [TextureLayer.sizeY]. */
+ public class TextureSizeUnit private constructor(@JvmField internal val value: Int) {
+ override fun toString(): String =
+ when (this) {
+ BRUSH_SIZE -> "BrushPaint.TextureSizeUnit.BRUSH_SIZE"
+ STROKE_SIZE -> "BrushPaint.TextureSizeUnit.STROKE_SIZE"
+ STROKE_COORDINATES -> "BrushPaint.TextureSizeUnit.STROKE_COORDINATES"
+ else -> "BrushPaint.TextureSizeUnit.INVALID($value)"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other === this) return true
+ if (other !is TextureSizeUnit) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+ /** As multiples of brush size. */
+ @JvmField public val BRUSH_SIZE: TextureSizeUnit = TextureSizeUnit(0)
+ /**
+ * As multiples of the stroke "size". This has different meanings depending on the value
+ * of [TextureMapping] for the given texture. For [TextureMapping.TILING] textures, the
+ * stroke size is equal to the dimensions of the XY bounding rectangle of the mesh. For
+ * [TextureMapping.WINDING] textures, the stroke size components are given by x: stroke
+ * width, which may change over the course of the stroke if behaviors affect the tip
+ * geometry. y: the total distance traveled by the stroke.
+ */
+ @JvmField public val STROKE_SIZE: TextureSizeUnit = TextureSizeUnit(1)
+ /** In the same units as the stroke's input positions and stored geometry. */
+ @JvmField public val STROKE_COORDINATES: TextureSizeUnit = TextureSizeUnit(2)
+ }
+ }
+
+ /**
+ * The method by which the combined texture layers (index <= i) are blended with the next layer.
+ * The blend mode on the final layer controls how the combined texture is blended with the brush
+ * color, and should typically be a mode whose output alpha is proportional to the destination
+ * alpha, so that it can be adjusted by anti-aliasing.
+ */
+ public class BlendMode private constructor(@JvmField internal val value: Int) {
+ override fun toString(): String =
+ when (this) {
+ MODULATE -> "BrushPaint.BlendMode.MODULATE"
+ DST_IN -> "BrushPaint.BlendMode.DST_IN"
+ DST_OUT -> "BrushPaint.BlendMode.DST_OUT"
+ SRC_ATOP -> "BrushPaint.BlendMode.SRC_ATOP"
+ SRC_IN -> "BrushPaint.BlendMode.SRC_IN"
+ SRC_OVER -> "BrushPaint.BlendMode.SRC_OVER"
+ else -> "BrushPaint.BlendMode.INVALID($value)"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other === this) return true
+ return other is BlendMode && this.value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+
+ /**
+ * Source and destination are component-wise multiplied, including opacity.
+ *
+ * ```
+ * Alpha = Alpha_src * Alpha_dst
+ * Color = Color_src * Color_dst
+ * ```
+ */
+ @JvmField public val MODULATE: BlendMode = BlendMode(0)
+ /**
+ * Keeps destination pixels that cover source pixels. Discards remaining source and
+ * destination pixels.
+ *
+ * ```
+ * Alpha = Alpha_src * Alpha_dst
+ * Color = Alpha_src * Color_dst
+ * ```
+ */
+ @JvmField public val DST_IN: BlendMode = BlendMode(1)
+ /**
+ * Keeps the destination pixels not covered by source pixels. Discards destination
+ * pixels that are covered by source pixels and all source pixels.
+ *
+ * ```
+ * Alpha = (1 - Alpha_src) * Alpha_dst
+ * Color = (1 - Alpha_src) * Color_dst
+ * ```
+ */
+ @JvmField public val DST_OUT: BlendMode = BlendMode(2)
+ /**
+ * Discards source pixels that do not cover destination pixels. Draws remaining pixels
+ * over destination pixels.
+ *
+ * ```
+ * Alpha = Alpha_dst
+ * Color = Alpha_dst * Color_src + (1 - Alpha_src) * Color_dst
+ * ```
+ */
+ @JvmField public val SRC_ATOP: BlendMode = BlendMode(3)
+ /**
+ * Keeps the source pixels that cover destination pixels. Discards remaining source and
+ * destination pixels.
+ *
+ * ```
+ * Alpha = Alpha_src * Alpha_dst
+ * Color = Color_src * Alpha_dst
+ * ```
+ */
+ @JvmField public val SRC_IN: BlendMode = BlendMode(4)
+
+ /*
+ * The following modes can't be used for the last TextureLayer, which defines the mode for
+ * blending the combined texture with the (possibly adjusted per-vertex) brush color. That blend
+ * mode needs the output Alpha to be a multiple of Alpha_dst so that per-vertex adjustment for
+ * anti-aliasing is preserved correctly.
+ */
+
+ /**
+ * The source pixels are drawn over the destination pixels.
+ *
+ * ```
+ * Alpha = Alpha_src + (1 - Alpha_src) * Alpha_dst
+ * Color = Color_src + (1 - Alpha_src) * Color_dst
+ * ```
+ *
+ * This mode shouldn't normally be used for the final [TextureLayer], since its output
+ * alpha is not proportional to the destination alpha (so it wouldn't preserve alpha
+ * adjustments from anti-aliasing).
+ */
+ @JvmField public val SRC_OVER: BlendMode = BlendMode(5)
+ /**
+ * The source pixels are drawn behind the destination pixels.
+ *
+ * ```
+ * Alpha = Alpha_dst + (1 - Alpha_dst) * Alpha_src
+ * Color = Color_dst + (1 - Alpha_dst) * Color_src
+ * ```
+ *
+ * This mode shouldn't normally be used for the final [TextureLayer], since its output
+ * alpha is not proportional to the destination alpha (so it wouldn't preserve alpha
+ * adjustments from anti-aliasing).
+ */
+ @JvmField public val DST_OVER: BlendMode = BlendMode(6)
+ /**
+ * Keeps the source pixels and discards the destination pixels.
+ *
+ * ```
+ * Alpha = Alpha_src
+ * Color = Color_src
+ * ```
+ *
+ * This mode shouldn't normally be used for the final [TextureLayer], since its output
+ * alpha is not proportional to the destination alpha (so it wouldn't preserve alpha
+ * adjustments from anti-aliasing).
+ */
+ @JvmField public val SRC: BlendMode = BlendMode(7)
+ /**
+ * Keeps the destination pixels and discards the source pixels.
+ *
+ * ```
+ * Alpha = Alpha_dst
+ * Color = Color_dst
+ * ```
+ *
+ * This mode is unlikely to be useful, since it effectively causes the renderer to just
+ * ignore this [TextureLayer] and all layers before it, but it is included for
+ * completeness.
+ */
+ @JvmField public val DST: BlendMode = BlendMode(8)
+ /**
+ * Keeps the source pixels that do not cover destination pixels. Discards destination
+ * pixels and all source pixels that cover destination pixels.
+ *
+ * ```
+ * Alpha = (1 - Alpha_dst) * Alpha_src
+ * Color = (1 - Alpha_dst) * Color_src
+ * ```
+ *
+ * This mode shouldn't normally be used for the final [TextureLayer], since its output
+ * alpha is not proportional to the destination alpha (so it wouldn't preserve alpha
+ * adjustments from anti-aliasing).
+ */
+ @JvmField public val SRC_OUT: BlendMode = BlendMode(9)
+ /**
+ * Discards destination pixels that aren't covered by source pixels. Remaining
+ * destination pixels are drawn over source pixels.
+ *
+ * ```
+ * Alpha = Alpha_src
+ * Color = Alpha_src * Color_dst + (1 - Alpha_dst) * Color_src
+ * ```
+ *
+ * This mode shouldn't normally be used for the final [TextureLayer], since its output
+ * alpha is not proportional to the destination alpha (so it wouldn't preserve alpha
+ * adjustments from anti-aliasing).
+ */
+ @JvmField public val DST_ATOP: BlendMode = BlendMode(10)
+ /**
+ * Discards source and destination pixels that intersect; keeps source and destination
+ * pixels that do not intersect.
+ *
+ * ```
+ * Alpha = (1 - Alpha_dst) * Alpha_src + (1 - Alpha_src) * Alpha_dst
+ * Color = (1 - Alpha_dst) * Color_src + (1 - Alpha_src) * Color_dst
+ * ```
+ *
+ * This mode shouldn't normally be used for the final [TextureLayer], since its output
+ * alpha is not proportional to the destination alpha (so it wouldn't preserve alpha
+ * adjustments from anti-aliasing).
+ */
+ @JvmField public val XOR: BlendMode = BlendMode(11)
+ }
+ }
+
+ /**
+ * An explicit layer defined by an image.
+ *
+ * @param colorTextureUri The URI of an image that provides the color for a particular pixel for
+ * this layer. The coordinates within this image that will be used are determined by the other
+ * parameters.
+ * @param sizeX The X size in [TextureSizeUnit] of the image specified by [colorTextureUri].
+ * @param sizeY The Y size in [TextureSizeUnit] of the image specified by [colorTextureUri].
+ * @param offsetX An offset into the texture, specified as fractions of the texture [sizeX] in
+ * the range [0,1].
+ * @param offsetY An offset into the texture, specified as fractions of the texture [sizeY] in
+ * the range [0,1].
+ * @param rotation Angle in radians specifying the rotation of the texture. The rotation is
+ * carried out about the center of the texture's first repetition along both axes.
+ * @param opacity Overall layer opacity in the range [0,1], where 0 is transparent and 1 is
+ * opaque.
+ * @param sizeUnit The units used to specify [sizeX] and [sizeY].
+ * @param mapping The method by which the coordinates of the [colorTextureUri] image will apply
+ * to the stroke.
+ * @param blendMode The method by which the texture layers up to this one (index <= i) are
+ * combined with the subsequent texture layer (index == i+1). For the last texture layer, this
+ * defines the method by which the texture layer is combined with the brush color (possibly
+ * after that color gets per-vertex adjustments).
+ */
+ @Suppress("NotCloseable") // Finalize is only used to free the native peer.
+ public class TextureLayer(
+ public val colorTextureUri: String,
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = false,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false,
+ )
+ public val sizeX: Float,
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = false,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false,
+ )
+ public val sizeY: Float,
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true)
+ public val offsetX: Float = 0f,
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true)
+ public val offsetY: Float = 0f,
+ @AngleRadiansFloat public val rotation: Float = 0F,
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true)
+ public val opacity: Float = 1f,
+ public val sizeUnit: TextureSizeUnit = TextureSizeUnit.STROKE_COORDINATES,
+ public val origin: TextureOrigin = TextureOrigin.STROKE_SPACE_ORIGIN,
+ public val mapping: TextureMapping = TextureMapping.TILING,
+ public val blendMode: BlendMode = BlendMode.MODULATE,
+ ) {
+ internal val nativePointer: Long =
+ nativeCreateTextureLayer(
+ colorTextureUri,
+ sizeX,
+ sizeY,
+ offsetX,
+ offsetY,
+ rotation,
+ opacity,
+ sizeUnit.value,
+ origin.value,
+ mapping.value,
+ blendMode.value,
+ )
+
+ /**
+ * Creates a copy of `this` and allows named properties to be altered while keeping the rest
+ * unchanged.
+ */
+ @JvmSynthetic
+ public fun copy(
+ colorTextureUri: String = this.colorTextureUri,
+ sizeX: Float = this.sizeX,
+ sizeY: Float = this.sizeY,
+ offsetX: Float = this.offsetX,
+ offsetY: Float = this.offsetY,
+ @AngleRadiansFloat rotation: Float = this.rotation,
+ opacity: Float = this.opacity,
+ sizeUnit: TextureSizeUnit = this.sizeUnit,
+ origin: TextureOrigin = this.origin,
+ mapping: TextureMapping = this.mapping,
+ blendMode: BlendMode = this.blendMode,
+ ): TextureLayer {
+ if (
+ colorTextureUri == this.colorTextureUri &&
+ sizeX == this.sizeX &&
+ sizeY == this.sizeY &&
+ offsetX == this.offsetX &&
+ offsetY == this.offsetY &&
+ rotation == this.rotation &&
+ opacity == this.opacity &&
+ sizeUnit == this.sizeUnit &&
+ origin == this.origin &&
+ mapping == this.mapping &&
+ blendMode == this.blendMode
+ ) {
+ return this
+ }
+ return TextureLayer(
+ colorTextureUri,
+ sizeX,
+ sizeY,
+ offsetX,
+ offsetY,
+ rotation,
+ opacity,
+ sizeUnit,
+ origin,
+ mapping,
+ blendMode,
+ )
+ }
+
+ /**
+ * Returns a [Builder] with values set equivalent to `this`. Java developers, use the
+ * returned builder to build a copy of a TextureLayer. Kotlin developers, see [copy] method.
+ */
+ public fun toBuilder(): Builder =
+ Builder(
+ colorTextureUri = this.colorTextureUri,
+ sizeX = this.sizeX,
+ sizeY = this.sizeY,
+ offsetX = this.offsetX,
+ offsetY = this.offsetY,
+ rotation = this.rotation,
+ opacity = this.opacity,
+ sizeUnit = this.sizeUnit,
+ origin = this.origin,
+ mapping = this.mapping,
+ blendMode = this.blendMode,
+ )
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is TextureLayer) return false
+ return colorTextureUri == other.colorTextureUri &&
+ sizeX == other.sizeX &&
+ sizeY == other.sizeY &&
+ offsetX == other.offsetX &&
+ offsetY == other.offsetY &&
+ rotation == other.rotation &&
+ opacity == other.opacity &&
+ sizeUnit == other.sizeUnit &&
+ origin == other.origin &&
+ mapping == other.mapping &&
+ blendMode == other.blendMode
+ }
+
+ override fun toString(): String =
+ "BrushPaint.TextureLayer(colorTextureUri=$colorTextureUri, sizeX=$sizeX, sizeY=$sizeY, " +
+ "offset=[$offsetX, $offsetY], rotation=$rotation, opacity=$opacity sizeUnit=$sizeUnit, origin=$origin, mapping=$mapping, " +
+ "blendMode=$blendMode)"
+
+ override fun hashCode(): Int {
+ var result = colorTextureUri.hashCode()
+ result = 31 * result + sizeX.hashCode()
+ result = 31 * result + sizeY.hashCode()
+ result = 31 * result + offsetX.hashCode()
+ result = 31 * result + offsetY.hashCode()
+ result = 31 * result + rotation.hashCode()
+ result = 31 * result + opacity.hashCode()
+ result = 31 * result + sizeUnit.hashCode()
+ result = 31 * result + origin.hashCode()
+ result = 31 * result + mapping.hashCode()
+ result = 31 * result + blendMode.hashCode()
+ return result
+ }
+
+ /** Delete native TextureLayer memory. */
+ protected fun finalize() {
+ // NOMUTANTS -- Not tested post garbage collection.
+ nativeFreeTextureLayer(nativePointer)
+ }
+
+ /**
+ * Builder for [TextureLayer].
+ *
+ * Construct from TextureLayer.toBuilder().
+ */
+ @Suppress(
+ "ScopeReceiverThis"
+ ) // Builder pattern supported for Java clients, despite being an anti-pattern in Kotlin.
+ public class Builder
+ internal constructor(
+ private var colorTextureUri: String,
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = false,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false,
+ )
+ private var sizeX: Float,
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = false,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false,
+ )
+ private var sizeY: Float,
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true)
+ private var offsetX: Float = 0f,
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true)
+ private var offsetY: Float = 0f,
+ @AngleRadiansFloat private var rotation: Float = 0F,
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true)
+ private var opacity: Float = 1f,
+ private var sizeUnit: TextureSizeUnit = TextureSizeUnit.STROKE_COORDINATES,
+ private var origin: TextureOrigin = TextureOrigin.STROKE_SPACE_ORIGIN,
+ private var mapping: TextureMapping = TextureMapping.TILING,
+ private var blendMode: BlendMode = BlendMode.MODULATE,
+ ) {
+ public fun setColorTextureUri(colorTextureUri: String): Builder = apply {
+ this.colorTextureUri = colorTextureUri
+ }
+
+ public fun setSizeX(sizeX: Float): Builder = apply { this.sizeX = sizeX }
+
+ public fun setSizeY(sizeY: Float): Builder = apply { this.sizeY = sizeY }
+
+ public fun setOffsetX(offsetX: Float): Builder = apply { this.offsetX = offsetX }
+
+ public fun setOffsetY(offsetY: Float): Builder = apply { this.offsetY = offsetY }
+
+ public fun setRotation(rotation: Float): Builder = apply { this.rotation = rotation }
+
+ public fun setOpacity(opacity: Float): Builder = apply { this.opacity = opacity }
+
+ public fun setSizeUnit(sizeUnit: TextureSizeUnit): Builder = apply {
+ this.sizeUnit = sizeUnit
+ }
+
+ public fun setOrigin(origin: TextureOrigin): Builder = apply { this.origin = origin }
+
+ public fun setMapping(mapping: TextureMapping): Builder = apply {
+ this.mapping = mapping
+ }
+
+ public fun setBlendMode(blendMode: BlendMode): Builder = apply {
+ this.blendMode = blendMode
+ }
+
+ public fun build(): TextureLayer =
+ TextureLayer(
+ colorTextureUri,
+ sizeX,
+ sizeY,
+ offsetX,
+ offsetY,
+ rotation,
+ opacity,
+ sizeUnit,
+ origin,
+ mapping,
+ blendMode,
+ )
+ }
+
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeCreateTextureLayer(
+ colorTextureUri: String,
+ sizeX: Float,
+ sizeY: Float,
+ offsetX: Float,
+ offsetY: Float,
+ rotation: Float,
+ opacity: Float,
+ sizeUnit: Int,
+ origin: Int,
+ mapping: Int,
+ blendMode: Int,
+ ): Long
+
+ /** Release the underlying memory allocated in [nativeCreateTextureLayer]. */
+ private external fun nativeFreeTextureLayer(
+ nativePointer: Long
+ ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ // To be extended by extension methods.
+ public companion object
+ }
+
+ // To be extended by extension methods.
+ public companion object {
+ init {
+ NativeLoader.load()
+ }
+ }
+}
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushTip.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushTip.kt
new file mode 100644
index 0000000..e226874
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushTip.kt
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.annotation.FloatRange
+import androidx.annotation.IntRange
+import androidx.annotation.RestrictTo
+import androidx.ink.geometry.Angle
+import androidx.ink.geometry.AngleRadiansFloat
+import androidx.ink.nativeloader.NativeLoader
+import java.util.Collections.unmodifiableList
+import kotlin.jvm.JvmStatic
+import kotlin.jvm.JvmSynthetic
+import kotlin.math.PI
+
+/**
+ * A [BrushTip] consists of parameters that control how stroke inputs are used to model the tip
+ * shape and color, and create vertices for the stroke mesh.
+ *
+ * The specification can be considered in two parts:
+ * 1. Parameters for the base shape of the tip as a function of [Brush] size.
+ * 2. An array of [BrushBehavior]s that allow dynamic properties of each input to augment the tip
+ * shape and color.
+ *
+ * Depending on the combination of values, the tip can be shaped as a rounded parallelogram, circle,
+ * or stadium. Through [BrushBehavior]s, the tip can produce a per-vertex HSLA color shift that can
+ * be used to augment the [Brush] color when drawing. The default values below produce a static
+ * circular tip shape with diameter equal to the [Brush] size and no color shift.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+@ExperimentalInkCustomBrushApi
+@Suppress("NotCloseable") // Finalize is only used to free the native peer.
+public class BrushTip(
+ /**
+ * 2D scale used to calculate the initial width and height of the tip shape relative to the
+ * brush size prior to applying [slant] and [rotation].
+ *
+ * The base width and height of the tip will be equal to the brush size multiplied by [scaleX]
+ * and [scaleY] respectively. Valid values must be finite and non-negative, with at least one
+ * value greater than zero.
+ */
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = true,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false
+ )
+ public val scaleX: Float = 1f,
+
+ /**
+ * 2D scale used to calculate the initial width and height of the tip shape relative to the
+ * brush size prior to applying [slant] and [rotation].
+ *
+ * The base width and height of the tip will be equal to the brush size multiplied by [scaleX]
+ * and [scaleY] respectively. Valid values must be finite and non-negative, with at least one
+ * value greater than zero.
+ */
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = true,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false
+ )
+ public val scaleY: Float = 1f,
+
+ /**
+ * A normalized value in the range [0, 1] that is used to calculate the initial radius of
+ * curvature for the tip's corners. A value of 0 results in sharp corners and a value of 1
+ * results in the maximum radius of curvature given the current tip dimensions.
+ */
+ @FloatRange(from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true)
+ public val cornerRounding: Float = 1f,
+
+ /**
+ * Angle in readians used to calculate the initial slant of the tip shape prior to applying
+ * [rotation].
+ *
+ * The value should be in the range [-π/2, π/2] radians, and represents the angle by which
+ * "vertical" lines of the tip shape will appear rotated about their intersection with the
+ * x-axis.
+ *
+ * More info: This property is similar to the single-arg CSS skew() transformation. Unlike skew,
+ * slant tries to preserve the perimeter of the tip shape as opposed to its area. This is akin
+ * to "pressing" a rectangle into a parallelogram with non-right angles while preserving the
+ * side lengths.
+ */
+ @FloatRange(from = -PI / 2, fromInclusive = true, to = PI / 2, toInclusive = true)
+ @AngleRadiansFloat
+ public val slant: Float = Angle.ZERO,
+
+ /**
+ * A unitless parameter in the range [0, 1] that controls the separation between two of the
+ * shape's corners prior to applying [rotation].
+ *
+ * The two corners affected lie toward the negative y-axis relative to the center of the tip
+ * shape. I.e. the "upper edge" of the shape if positive y is chosen to point "down" in stroke
+ * coordinates.
+ *
+ * If [scaleX] is not 0, different values of [pinch] produce the following shapes: A value of 0
+ * will leave the corners unaffected as a rectangle or parallelogram. Values between 0 and 1
+ * will bring the corners closer together to result in a (possibly slanted) trapezoidal shape. A
+ * value of 1 will make the two corners coincide and result in a triangular shape.
+ */
+ @FloatRange(from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true)
+ public val pinch: Float = 0f,
+
+ /**
+ * Angle in radians specifying the initial rotation of the tip shape after applying [scaleX],
+ * [scaleY], [pinch], and [slant].
+ */
+ @AngleRadiansFloat public val rotation: Float = Angle.ZERO,
+
+ /**
+ * Scales the opacity of the base brush color for this tip, independent of `brush_behavior`s. A
+ * possible example application is a highlighter brush.
+ *
+ * The multiplier must be in the range [0, 2] and the value ultimately applied can be modified
+ * by applicable `brush_behavior`s.
+ */
+ @FloatRange(from = 0.0, fromInclusive = true, to = 2.0, toInclusive = true)
+ public val opacityMultiplier: Float = 1f,
+
+ /**
+ * Parameter controlling emission of particles as a function of distance traveled by the stroke
+ * inputs.
+ *
+ * When this and [particleGapDurationMillis] are both zero, the stroke will be continuous,
+ * unless gaps are introduced dynamically by [BrushBehavior]s. Otherwise, the stroke will be
+ * made up of particles. A new particle will be emitted after at least
+ * [particleGapDistanceScale] * [Brush.size] distance has been traveled by the stoke inputs.
+ */
+ @FloatRange(
+ from = 0.0,
+ fromInclusive = true,
+ to = Double.POSITIVE_INFINITY,
+ toInclusive = false
+ )
+ public val particleGapDistanceScale: Float = 0f,
+
+ /**
+ * Parameter controlling emission of particles as a function of time elapsed along the stroke.
+ *
+ * When this and [particleGapDistanceScale] are both zero, the stroke will be continuous, unless
+ * gaps are introduced dynamically by `BrushBehavior`s. Otherwise, the stroke will be made up of
+ * particles. Particles will be emitted at most once every [particleGapDurationMillis].
+ */
+ @IntRange(from = 0L) public val particleGapDurationMillis: Long = 0L,
+
+ // The [behaviors] val below is a defensive copy of this parameter.
+ behaviors: List<BrushBehavior> = emptyList(),
+) {
+ /**
+ * A list of [BrushBehavior]s that allow dynamic properties of each input to augment the tip
+ * shape and color.
+ */
+ public val behaviors: List<BrushBehavior> = unmodifiableList(behaviors.toList())
+
+ /** A handle to the underlying native [BrushTip] object. */
+ internal val nativePointer: Long =
+ nativeCreateBrushTip(
+ scaleX,
+ scaleY,
+ cornerRounding,
+ slant,
+ pinch,
+ rotation,
+ opacityMultiplier,
+ particleGapDistanceScale,
+ particleGapDurationMillis,
+ behaviors.size,
+ )
+
+ init {
+ for (behavior in behaviors) {
+ nativeAppendBehavior(nativePointer, behavior.nativePointer)
+ }
+ }
+
+ /**
+ * Creates a copy of `this` and allows named properties to be altered while keeping the rest
+ * unchanged.
+ */
+ @JvmSynthetic
+ public fun copy(
+ scaleX: Float = this.scaleX,
+ scaleY: Float = this.scaleY,
+ cornerRounding: Float = this.cornerRounding,
+ @AngleRadiansFloat slant: Float = this.slant,
+ pinch: Float = this.pinch,
+ @AngleRadiansFloat rotation: Float = this.rotation,
+ opacityMultiplier: Float = this.opacityMultiplier,
+ particleGapDistanceScale: Float = this.particleGapDistanceScale,
+ particleGapDurationMillis: Long = this.particleGapDurationMillis,
+ behaviors: List<BrushBehavior> = this.behaviors,
+ ): BrushTip =
+ BrushTip(
+ scaleX,
+ scaleY,
+ cornerRounding,
+ slant,
+ pinch,
+ rotation,
+ opacityMultiplier,
+ particleGapDistanceScale,
+ particleGapDurationMillis,
+ behaviors,
+ )
+
+ /**
+ * Returns a [Builder] with values set equivalent to `this`. Java developers, use the returned
+ * builder to build a copy of a BrushTip. Kotlin developers, see [copy] method.
+ */
+ public fun toBuilder(): Builder =
+ Builder()
+ .setScaleX(scaleX)
+ .setScaleY(scaleY)
+ .setCornerRounding(cornerRounding)
+ .setSlant(slant)
+ .setPinch(pinch)
+ .setRotation(rotation)
+ .setOpacityMultiplier(opacityMultiplier)
+ .setParticleGapDistanceScale(particleGapDistanceScale)
+ .setParticleGapDurationMillis(particleGapDurationMillis)
+ .setBehaviors(behaviors)
+
+ /**
+ * Builder for [BrushTip].
+ *
+ * Use BrushTip.Builder to construct a [BrushTip] with default values, overriding only as
+ * needed.
+ */
+ @Suppress("ScopeReceiverThis")
+ public class Builder {
+ private var scaleX: Float = 1f
+ private var scaleY: Float = 1f
+ private var cornerRounding: Float = 1f
+ private var slant: Float = Angle.ZERO
+ private var pinch: Float = 0f
+ private var rotation: Float = Angle.ZERO
+ private var opacityMultiplier: Float = 1f
+ private var particleGapDistanceScale: Float = 0F
+ private var particleGapDurationMillis: Long = 0L
+ private var behaviors: List<BrushBehavior> = emptyList()
+
+ public fun setScaleX(scaleX: Float): Builder = apply { this.scaleX = scaleX }
+
+ public fun setScaleY(scaleY: Float): Builder = apply { this.scaleY = scaleY }
+
+ public fun setCornerRounding(cornerRounding: Float): Builder = apply {
+ this.cornerRounding = cornerRounding
+ }
+
+ public fun setSlant(slant: Float): Builder = apply { this.slant = slant }
+
+ public fun setPinch(pinch: Float): Builder = apply { this.pinch = pinch }
+
+ public fun setRotation(rotation: Float): Builder = apply { this.rotation = rotation }
+
+ public fun setOpacityMultiplier(opacityMultiplier: Float): Builder = apply {
+ this.opacityMultiplier = opacityMultiplier
+ }
+
+ public fun setParticleGapDistanceScale(particleGapDistanceScale: Float): Builder = apply {
+ this.particleGapDistanceScale = particleGapDistanceScale
+ }
+
+ public fun setParticleGapDurationMillis(particleGapDurationMillis: Long): Builder = apply {
+ this.particleGapDurationMillis = particleGapDurationMillis
+ }
+
+ public fun setBehaviors(behaviors: List<BrushBehavior>): Builder = apply {
+ this.behaviors = behaviors.toList()
+ }
+
+ public fun build(): BrushTip =
+ BrushTip(
+ scaleX,
+ scaleY,
+ cornerRounding,
+ slant,
+ pinch,
+ rotation,
+ opacityMultiplier,
+ particleGapDistanceScale,
+ particleGapDurationMillis,
+ behaviors,
+ )
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is BrushTip) return false
+ return scaleY == other.scaleY &&
+ scaleX == other.scaleX &&
+ pinch == other.pinch &&
+ cornerRounding == other.cornerRounding &&
+ slant == other.slant &&
+ rotation == other.rotation &&
+ particleGapDistanceScale == other.particleGapDistanceScale &&
+ particleGapDurationMillis == other.particleGapDurationMillis &&
+ opacityMultiplier == other.opacityMultiplier &&
+ behaviors == other.behaviors
+ }
+
+ override fun hashCode(): Int {
+ var result = scaleX.hashCode()
+ result = 31 * result + scaleY.hashCode()
+ result = 31 * result + pinch.hashCode()
+ result = 31 * result + cornerRounding.hashCode()
+ result = 31 * result + slant.hashCode()
+ result = 31 * result + rotation.hashCode()
+ result = 31 * result + opacityMultiplier.hashCode()
+ result = 31 * result + particleGapDistanceScale.hashCode()
+ result = 31 * result + particleGapDurationMillis.hashCode()
+ result = 31 * result + behaviors.hashCode()
+ return result
+ }
+
+ override fun toString(): String =
+ "BrushTip(scale=($scaleX, $scaleY), cornerRounding=$cornerRounding," +
+ " slant=$slant, pinch=$pinch, rotation=$rotation, opacityMultiplier=$opacityMultiplier," +
+ " particleGapDistanceScale=$particleGapDistanceScale," +
+ " particleGapDurationMillis=$particleGapDurationMillis, behaviors=$behaviors)"
+
+ /** Delete native BrushTip memory. */
+ protected fun finalize() {
+ // NOMUTANTS -- Not tested post garbage collection.
+ nativeFreeBrushTip(nativePointer)
+ }
+
+ /** Create underlying native object and return reference for all subsequent native calls. */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeCreateBrushTip(
+ scaleX: Float,
+ scaleY: Float,
+ cornerRounding: Float,
+ slant: Float,
+ pinch: Float,
+ rotation: Float,
+ opacityMultiplier: Float,
+ particleGapDistanceScale: Float,
+ particleGapDurationMillis: Long,
+ behaviorsCount: Int,
+ ): Long
+
+ /**
+ * Appends a texture layer to a *mutable* C++ BrushTip object as referenced by [nativePointer].
+ * Only call during init{} so to keep this BrushTip object immutable after construction and
+ * equivalent across Kotlin and C++.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun nativeAppendBehavior(tipNativePointer: Long, behaviorNativePointer: Long)
+
+ /** Release the underlying memory allocated in [nativeCreateBrushTip]. */
+ private external fun nativeFreeBrushTip(
+ nativePointer: Long
+ ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ // Companion object gets initialized before anything else.
+ public companion object {
+ init {
+ NativeLoader.load()
+ }
+
+ /** Returns a new [BrushTip.Builder]. */
+ @JvmStatic public fun builder(): Builder = Builder()
+ }
+}
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/ColorExtensions.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/ColorExtensions.kt
new file mode 100644
index 0000000..889fab9
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/ColorExtensions.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.annotation.RestrictTo
+import androidx.ink.brush.color.Color as ComposeColor
+import androidx.ink.brush.color.colorspace.ColorSpace as ComposeColorSpace
+import androidx.ink.brush.color.colorspace.ColorSpaces as ComposeColorSpaces
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public fun ComposeColor.toColorInInkSupportedColorSpace(): ComposeColor {
+ return if (this.colorSpace.isSupportedInInk()) {
+ this
+ } else {
+ this.convert(ComposeColorSpaces.DisplayP3)
+ }
+}
+
+internal fun ComposeColorSpace.toInkColorSpaceId() =
+ when (this) {
+ ComposeColorSpaces.Srgb -> 0
+ ComposeColorSpaces.DisplayP3 -> 1
+ else -> throw IllegalArgumentException("Unsupported Compose color space")
+ }
+
+internal fun ComposeColorSpace.isSupportedInInk() =
+ (this == ComposeColorSpaces.Srgb || this == ComposeColorSpaces.DisplayP3)
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/EasingFunction.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/EasingFunction.kt
new file mode 100644
index 0000000..b39a502
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/EasingFunction.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.annotation.FloatRange
+import androidx.annotation.RestrictTo
+import androidx.ink.geometry.ImmutableVec
+import java.util.Collections.unmodifiableList
+import kotlin.jvm.JvmField
+
+/**
+ * An easing function always passes through the (x, y) points (0, 0) and (1, 1). It typically acts
+ * to map x values in the [0, 1] interval to y values in [0, 1] by either one of the predefined or
+ * one of the parameterized curve types below. Depending on the type of curve, input and output
+ * values outside [0, 1] are possible.
+ */
+@ExperimentalInkCustomBrushApi
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+public abstract class EasingFunction private constructor() {
+
+ public class Predefined private constructor(@JvmField internal val value: Int) :
+ EasingFunction() {
+
+ public fun toSimpleString(): String =
+ when (value) {
+ 0 -> "LINEAR"
+ 1 -> "EASE"
+ 2 -> "EASE_IN"
+ 3 -> "EASE_OUT"
+ 4 -> "EASE_IN_OUT"
+ 5 -> "STEP_START"
+ 6 -> "STEP_END"
+ else -> "INVALID"
+ }
+
+ override fun toString(): String = PREFIX + toSimpleString()
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is Predefined) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+ /** The linear identity function: accepts and returns values outside [0, 1]. */
+ @JvmField public val LINEAR: Predefined = Predefined(0)
+
+ /**
+ * Predefined cubic Bezier function. See
+ * [ease](https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions)
+ * and @see [CubicBezier] about input values outside [0, 1])
+ */
+ @JvmField public val EASE: Predefined = Predefined(1)
+
+ /**
+ * Predefined cubic Bezier function. See
+ * [ease-in](https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions)
+ * and @see [CubicBezier] about input values outside [0, 1])
+ */
+ @JvmField public val EASE_IN: Predefined = Predefined(2)
+
+ /**
+ * Predefined cubic Bezier function. See
+ * [ease-out](https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions)
+ * and @see [CubicBezier] about input values outside [0, 1])
+ */
+ @JvmField public val EASE_OUT: Predefined = Predefined(3)
+
+ /**
+ * Predefined cubic Bezier function. See
+ * [ease-in-out](https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions)
+ * and @see [CubicBezier] about input values outside [0, 1])
+ */
+ @JvmField public val EASE_IN_OUT: Predefined = Predefined(4)
+
+ /**
+ * Predefined step function with a jump-start at input progress value of 0. See
+ * [step start](https://www.w3.org/TR/css-easing-1/#step-easing-functions)
+ */
+ @JvmField public val STEP_START: Predefined = Predefined(5)
+
+ /**
+ * Predefined step function with a jump-end at input progress value of 1. See
+ * [step end](https://www.w3.org/TR/css-easing-1/#step-easing-functions)
+ */
+ @JvmField public val STEP_END: Predefined = Predefined(6)
+
+ private const val PREFIX = "EasingFunction.Predefined."
+ }
+ }
+
+ /**
+ * Parameters for a custom cubic Bezier easing function.
+ *
+ * A cubic Bezier is generally defined by four points, P0 - P3. In the case of the easing
+ * function, P0 is defined to be the point (0, 0), and P3 is defined to be the point (1, 1). The
+ * values of [x1] and [x2] are required to be in the range [0, 1]. This guarantees that the
+ * resulting curve is a function with respect to x and follows the CSS cubic Bezier
+ * specification:
+ * [https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function#cubic_b%C3%A9zier_easing_function](https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function#cubic_b%C3%A9zier_easing_function)
+ *
+ * Valid parameters must have all finite values, and [x1] and [x2] must be in the interval
+ * [0, 1].
+ *
+ * Input x values that are outside the interval [0, 1] will be clamped, but output values will
+ * not. This is somewhat different from the w3c defined cubic Bezier that allows extrapolated
+ * values outside x in [0, 1] by following end-point tangents.
+ */
+ public class CubicBezier(
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true)
+ public val x1: Float,
+ public val y1: Float,
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true)
+ public val x2: Float,
+ public val y2: Float,
+ ) : EasingFunction() {
+ init {
+ require(x1.isFinite() && x2.isFinite() && y1.isFinite() && y2.isFinite()) {
+ "All parameters must be finite. x1 = $x1, x2 = $x2, y1 = $y1, y2 = $y2"
+ }
+ require(x1 in 0.0..1.0) { "x1 = $x1 is required to be in the range [0, 1]" }
+ require(x2 in 0.0..1.0) { "x2 = $x2 is required to be in the range [0, 1]" }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is CubicBezier) {
+ return false
+ }
+ return x1 == other.x1 && x2 == other.x2 && y1 == other.y1 && y2 == other.y2
+ }
+
+ override fun hashCode(): Int {
+ var result = x1.hashCode()
+ result = 31 * result + x2.hashCode()
+ result = 31 * result + y1.hashCode()
+ result = 31 * result + y2.hashCode()
+ return result
+ }
+
+ override fun toString(): String =
+ "EasingFunction.CubicBezier(x1=$x1, y1=$y1, x2=$x2, y2=$y2)"
+
+ // Declared to make extension functions available.
+ public companion object
+ }
+
+ /**
+ * Parameters for a custom piecewise-linear easing function.
+ *
+ * A piecewise-linear function is defined by a sequence of points; the value of the function at
+ * an x-position equal to one of those points is equal to the y-position of that point, and the
+ * value of the function at an x-position between two points is equal to the linear
+ * interpolation between those points' y-positions. This easing function implicitly includes the
+ * points (0, 0) and (1, 1), so the `points` field below need only include any points between
+ * those. If [points] is empty, then this function is equivalent to the [Predefined.LINEAR]
+ * identity function.
+ *
+ * To be valid, all y-positions must be finite, and all x-positions must be in the range [0, 1]
+ * and must be monotonically non-decreasing. It is valid for multiple points to have the same
+ * x-position, in order to create a discontinuity in the function; in that case, the value of
+ * the function at exactly that x-position is equal to the y-position of the last of these
+ * points.
+ *
+ * If the input x-value is outside the interval [0, 1], the output will be extrapolated from the
+ * first/last line segment.
+ */
+ public class Linear(
+ // The [points] val below is a defensive copy of this parameter.
+ points: List<ImmutableVec>
+ ) : EasingFunction() {
+ public val points: List<ImmutableVec> = unmodifiableList(points.toList())
+
+ init {
+ for (point: ImmutableVec in points) {
+ require(point.x.isFinite() && point.y.isFinite()) {
+ "All points must be finite. Got $point"
+ }
+ require(point.x in 0.0..1.0) {
+ "point.x is required to be in the range [0, 1]. Got $point"
+ }
+ }
+ for ((a, b) in points.zipWithNext()) {
+ require(a.x <= b.x) { "Points must be sorted by x-value. Got $a before $b" }
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is Linear) {
+ return false
+ }
+ return points == other.points
+ }
+
+ override fun hashCode(): Int {
+ return points.hashCode()
+ }
+
+ override fun toString(): String = "EasingFunction.Linear(${points})"
+
+ // Declared to make extension functions available.
+ public companion object
+ }
+
+ /**
+ * Parameters for a custom step easing function.
+ *
+ * A step function is defined by the number of equal-sized steps into which the
+ * [0, 1) interval of input-x is split and the behavior at the extremes. When x < 0, the output will always be 0. When x >= 1, the output will always be 1. The output of the first and last steps is governed by the [StepPosition].
+ *
+ * @param stepCount The number of steps. Must always be greater than 0, and must be greater than
+ * 1 if [stepPosition] is [StepPosition.JUMP_NONE].
+ *
+ * The behavior and naming follows the CSS steps() specification at
+ * [https://www.w3.org/TR/css-easing-1/#step-easing-functions](https://www.w3.org/TR/css-easing-1/#step-easing-functions)
+ */
+ public class Steps(public val stepCount: Int, public val stepPosition: StepPosition) :
+ EasingFunction() {
+ init {
+ require(stepCount > 0) { "stepCount = $stepCount is required to be greater than 0." }
+ require(stepPosition != StepPosition.JUMP_NONE || stepCount > 1) {
+ "stepCount = $stepCount is required to be greater than 1 if stepPosition = JUMP_NONE."
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is Steps) {
+ return false
+ }
+ return stepCount == other.stepCount && stepPosition == other.stepPosition
+ }
+
+ override fun hashCode(): Int {
+ var result = stepCount.hashCode()
+ result = 31 * result + stepPosition.hashCode()
+ return result
+ }
+
+ override fun toString(): String =
+ "EasingFunction.Steps(stepCount=$stepCount, stepPosition=$stepPosition)"
+
+ // Declared to make extension functions available.
+ public companion object
+ }
+
+ /**
+ * Setting to determine the desired output value of the first and last step of
+ * [0, 1) for [EasingFunction.Steps].
+ */
+ public class StepPosition private constructor(@JvmField internal val value: Int) :
+ EasingFunction() {
+
+ public fun toSimpleString(): String =
+ when (value) {
+ 0 -> "JUMP_END"
+ 1 -> "JUMP_START"
+ 2 -> "JUMP_BOTH"
+ 3 -> "JUMP_NONE"
+ else -> "INVALID"
+ }
+
+ override fun toString(): String = PREFIX + toSimpleString()
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other !is StepPosition) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+ /**
+ * The step function "jumps" at the end of [0, 1): For x in [0, 1/step_count) => y = 0.
+ * For x in [1 - 1/step_count, 1) => y = 1 - 1/step_count.
+ */
+ @JvmField public val JUMP_END: StepPosition = StepPosition(0)
+ /**
+ * The step function "jumps" at the start of [0, 1): For x in [0, 1/step_count) => y =
+ * 1/step_count. For x in [1 - 1/step_count, 1) => y = 1.
+ */
+ @JvmField public val JUMP_START: StepPosition = StepPosition(1)
+ /**
+ * The step function "jumps" at both the start and the end: For x in [0, 1/step_count)
+ * => y = 1/(step_count + 1). For x in [1 - 1/step_count, 1) => y = 1 - 1/(step_count +
+ * 1).
+ */
+ @JvmField public val JUMP_BOTH: StepPosition = StepPosition(2)
+
+ /**
+ * The step function does not "jump" at either boundary: For x in [0, 1/step_count) => y
+ * = 0. For x in [1 - 1/step_count, 1) => y = 1.
+ */
+ @JvmField public val JUMP_NONE: StepPosition = StepPosition(3)
+ private const val PREFIX = "EasingFunction.StepPosition."
+ }
+ }
+}
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/ExperimentalInkCustomBrushApi.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/ExperimentalInkCustomBrushApi.kt
new file mode 100644
index 0000000..c422f93
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/ExperimentalInkCustomBrushApi.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+/**
+ * Marks declarations that are are part of the **experimental** Ink brush customization API. These
+ * declarations may (or may not) be changed, deprecated, or removed in the near future, or the
+ * semantics of their behavior may change in some way that may break some code.
+ *
+ * You can opt in to using APIs in your code by marking your declaration with `@OptIn` passing the
+ * opt-in requirement annotation as its argument: `@OptIn(ExperimentalInkCustomBrushApi::class)`.
+ */
+@MustBeDocumented
+@Retention(value = AnnotationRetention.BINARY)
+@Target(
+ AnnotationTarget.CLASS,
+ AnnotationTarget.ANNOTATION_CLASS,
+ AnnotationTarget.PROPERTY,
+ AnnotationTarget.FIELD,
+ AnnotationTarget.LOCAL_VARIABLE,
+ AnnotationTarget.VALUE_PARAMETER,
+ AnnotationTarget.CONSTRUCTOR,
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER,
+ AnnotationTarget.TYPEALIAS,
+)
+@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
+public annotation class ExperimentalInkCustomBrushApi
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/InputToolType.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/InputToolType.kt
new file mode 100644
index 0000000..90626ce
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/InputToolType.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.annotation.RestrictTo
+import kotlin.jvm.JvmName
+import kotlin.jvm.JvmStatic
+
+/**
+ * The type of input tool used in producing [com.google.inputmethod.ink.strokes.StrokeInput], used
+ * by [BrushBehavior] to define when a behavior is applicable.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+
+// TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard config file
+// instead.
+public class InputToolType
+private constructor(
+ @JvmField
+ @field:RestrictTo(
+ RestrictTo.Scope.LIBRARY_GROUP
+ ) // NonPublicApi // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in
+ // Proguard config file instead.
+ public val value: Int
+) {
+
+ private fun toSimpleString(): String =
+ when (this) {
+ UNKNOWN -> "UNKNOWN"
+ MOUSE -> "MOUSE"
+ TOUCH -> "TOUCH"
+ STYLUS -> "STYLUS"
+ else -> "INVALID"
+ }
+
+ public override fun toString(): String = PREFIX + this.toSimpleString()
+
+ public override fun equals(other: Any?): Boolean {
+ if (other == null || other !is InputToolType) return false
+ return value == other.value
+ }
+
+ public override fun hashCode(): Int = value.hashCode()
+
+ public companion object {
+ /**
+ * Get InputToolType by Int. Accessible internally for conversion to and from C++
+ * representations of ToolType in JNI code and in internal Kotlin code. The `internal`
+ * keyword obfuscates the function signature, hence the need for JvmName annotation.
+ */
+ @JvmStatic
+ @JvmName("from")
+ // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard
+ // config file instead.
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public fun from(value: Int): InputToolType {
+ return when (value) {
+ UNKNOWN.value -> UNKNOWN
+ MOUSE.value -> MOUSE
+ TOUCH.value -> TOUCH
+ STYLUS.value -> STYLUS
+ else -> throw IllegalArgumentException("Invalid value: $value")
+ }
+ }
+
+ @JvmField public val UNKNOWN: InputToolType = InputToolType(0)
+ @JvmField public val MOUSE: InputToolType = InputToolType(1)
+ @JvmField public val TOUCH: InputToolType = InputToolType(2)
+ @JvmField public val STYLUS: InputToolType = InputToolType(3)
+ private const val PREFIX = "InputToolType."
+ }
+}
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/StockBrushes.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/StockBrushes.kt
new file mode 100644
index 0000000..5cb0b21
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/StockBrushes.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.annotation.RestrictTo
+import androidx.ink.geometry.Angle
+import kotlin.jvm.JvmStatic
+
+/**
+ * Provides a fixed set of stock [BrushFamily] objects that any app can use.
+ *
+ * All brush designs are versioned, so apps can safely store input points and brush specs instead of
+ * the pixel result, but be able to regenerate strokes from stored input points that look like the
+ * strokes originally drawn by the user. Brush designs are intended to evolve over time, and are
+ * released as update packs to the stock library.
+ *
+ * Each successive brush version will keep to the spirit of the brush, but the actual effect can
+ * change between versions. For example, a new version of the highlighter may introduce a variation
+ * on how round the tip is, or what sort of curve maps color to pressure.
+ *
+ * We generally recommend that applications use the latest brush version available; but some use
+ * cases, such as art, should be careful to track which version of a brush was used if the document
+ * is regenerated, so that the user gets the same visual result.
+ */
+@OptIn(ExperimentalInkCustomBrushApi::class)
+public object StockBrushes {
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ // Needed on both property and on getter for AndroidX build, but the Kotlin compiler doesn't
+ // like it on the getter so suppress its complaint.
+ @ExperimentalInkCustomBrushApi
+ @get:ExperimentalInkCustomBrushApi
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @JvmStatic
+ public val predictionFadeOutBehavior: BrushBehavior =
+ BrushBehavior(
+ targetNodes =
+ listOf(
+ BrushBehavior.TargetNode(
+ target = BrushBehavior.Target.OPACITY_MULTIPLIER,
+ targetModifierRangeLowerBound = 1F,
+ targetModifierRangeUpperBound = 0.3F,
+ BrushBehavior.BinaryOpNode(
+ operation = BrushBehavior.BinaryOp.PRODUCT,
+ firstInput =
+ BrushBehavior.SourceNode(
+ source = BrushBehavior.Source.PREDICTED_TIME_ELAPSED_IN_MILLIS,
+ sourceValueRangeLowerBound = 0F,
+ sourceValueRangeUpperBound = 24F,
+ ),
+ // The second branch of the binary op node keeps the opacity fade-out
+ // from starting
+ // until the predicted inputs have traveled at least 1.5x brush-size.
+ secondInput =
+ BrushBehavior.ResponseNode(
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ input =
+ BrushBehavior.SourceNode(
+ source =
+ BrushBehavior.Source
+ .PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE,
+ sourceValueRangeLowerBound = 1.5F,
+ sourceValueRangeUpperBound = 2F,
+ ),
+ ),
+ ),
+ )
+ )
+ )
+
+ /**
+ * Version 1 of a simple, circular fixed-width brush.
+ *
+ * The behavior of this [BrushFamily] will not meaningfully change in future releases. More
+ * significant updates would be contained in a [BrushFamily] with a different name specifying a
+ * later version number.
+ */
+ @JvmStatic
+ public val markerV1: BrushFamily =
+ BrushFamily(tip = BrushTip(behaviors = listOf(predictionFadeOutBehavior)))
+
+ /**
+ * The latest version of a simple, circular fixed-width brush.
+ *
+ * The behavior of this [BrushFamily] may change in future releases, as it always points to the
+ * latest version of the marker.
+ */
+ @JvmStatic public val markerLatest: BrushFamily = markerV1
+
+ /**
+ * Version 1 of a pressure- and speed-sensitive brush that is optimized for handwriting with a
+ * stylus.
+ *
+ * The behavior of this [BrushFamily] will not meaningfully change in future releases. More
+ * significant updates would be contained in a [BrushFamily] with a different name specifying a
+ * later version number.
+ */
+ @JvmStatic
+ public val pressurePenV1: BrushFamily =
+ BrushFamily(
+ tip =
+ BrushTip(
+ behaviors =
+ listOf(
+ predictionFadeOutBehavior,
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.SIZE_MULTIPLIER,
+ sourceValueRangeLowerBound = 0f,
+ sourceValueRangeUpperBound = 1f,
+ targetModifierRangeLowerBound = 0.05f,
+ targetModifierRangeUpperBound = 1f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.LINEAR,
+ responseTimeMillis = 40L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ ),
+ )
+ )
+ )
+
+ /**
+ * The latest version of a pressure- and speed-sensitive brush that is optimized for handwriting
+ * with a stylus.
+ *
+ * The behavior of this [BrushFamily] may change in future releases, as it always points to the
+ * latest version of the pressure pen.
+ */
+ @JvmStatic public val pressurePenLatest: BrushFamily = pressurePenV1
+
+ /**
+ * Version 1 of a chisel-tip brush that is intended for highlighting text in a document (when
+ * used with a translucent brush color).
+ *
+ * The behavior of this [BrushFamily] will not meaningfully change in future releases. More
+ * significant updates would be contained in a [BrushFamily] with a different name specifying a
+ * later version number.
+ */
+ @JvmStatic
+ public val highlighterV1: BrushFamily =
+ BrushFamily(
+ tip =
+ BrushTip(
+ scaleX = 0.05f,
+ scaleY = 1f,
+ cornerRounding = 0.11f,
+ rotation = Angle.degreesToRadians(150f),
+ behaviors = listOf(predictionFadeOutBehavior),
+ )
+ )
+
+ /**
+ * Version 1 of a chisel-tip brush that is intended for highlighting text in a document (when
+ * used with a translucent brush color).
+ *
+ * The behavior of this [BrushFamily] may change in future releases, as it always points to the
+ * latest version of the pressure pen.
+ */
+ @JvmStatic public val highlighterLatest: BrushFamily = highlighterV1
+}
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushBehaviorTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushBehaviorTest.kt
new file mode 100644
index 0000000..9fc1779
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushBehaviorTest.kt
@@ -0,0 +1,1461 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.IllegalArgumentException
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalInkCustomBrushApi::class)
+@RunWith(JUnit4::class)
+class BrushBehaviorTest {
+
+ @Test
+ fun sourceConstants_areDistinct() {
+ val list =
+ listOf<BrushBehavior.Source>(
+ BrushBehavior.Source.CONSTANT_ZERO,
+ BrushBehavior.Source.NORMALIZED_PRESSURE,
+ BrushBehavior.Source.TILT_IN_RADIANS,
+ BrushBehavior.Source.TILT_X_IN_RADIANS,
+ BrushBehavior.Source.TILT_Y_IN_RADIANS,
+ BrushBehavior.Source.ORIENTATION_IN_RADIANS,
+ BrushBehavior.Source.ORIENTATION_ABOUT_ZERO_IN_RADIANS,
+ BrushBehavior.Source.SPEED_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND,
+ BrushBehavior.Source.VELOCITY_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND,
+ BrushBehavior.Source.VELOCITY_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND,
+ BrushBehavior.Source.DIRECTION_IN_RADIANS,
+ BrushBehavior.Source.DIRECTION_ABOUT_ZERO_IN_RADIANS,
+ BrushBehavior.Source.NORMALIZED_DIRECTION_X,
+ BrushBehavior.Source.NORMALIZED_DIRECTION_Y,
+ BrushBehavior.Source.DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE,
+ BrushBehavior.Source.TIME_OF_INPUT_IN_SECONDS,
+ BrushBehavior.Source.TIME_OF_INPUT_IN_MILLIS,
+ BrushBehavior.Source.PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE,
+ BrushBehavior.Source.PREDICTED_TIME_ELAPSED_IN_SECONDS,
+ BrushBehavior.Source.PREDICTED_TIME_ELAPSED_IN_MILLIS,
+ BrushBehavior.Source.DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE,
+ BrushBehavior.Source.TIME_SINCE_INPUT_IN_SECONDS,
+ BrushBehavior.Source.TIME_SINCE_INPUT_IN_MILLIS,
+ BrushBehavior.Source.ACCELERATION_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED,
+ BrushBehavior.Source.ACCELERATION_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED,
+ BrushBehavior.Source.ACCELERATION_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED,
+ BrushBehavior.Source
+ .ACCELERATION_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED,
+ BrushBehavior.Source
+ .ACCELERATION_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED,
+ BrushBehavior.Source.INPUT_SPEED_IN_CENTIMETERS_PER_SECOND,
+ BrushBehavior.Source.INPUT_VELOCITY_X_IN_CENTIMETERS_PER_SECOND,
+ BrushBehavior.Source.INPUT_VELOCITY_Y_IN_CENTIMETERS_PER_SECOND,
+ BrushBehavior.Source.INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS,
+ BrushBehavior.Source.PREDICTED_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS,
+ BrushBehavior.Source.INPUT_ACCELERATION_IN_CENTIMETERS_PER_SECOND_SQUARED,
+ BrushBehavior.Source.INPUT_ACCELERATION_X_IN_CENTIMETERS_PER_SECOND_SQUARED,
+ BrushBehavior.Source.INPUT_ACCELERATION_Y_IN_CENTIMETERS_PER_SECOND_SQUARED,
+ BrushBehavior.Source.INPUT_ACCELERATION_FORWARD_IN_CENTIMETERS_PER_SECOND_SQUARED,
+ BrushBehavior.Source.INPUT_ACCELERATION_LATERAL_IN_CENTIMETERS_PER_SECOND_SQUARED,
+ )
+ assertThat(list.toSet()).hasSize(list.size)
+ }
+
+ @Test
+ fun sourceHashCode_withIdenticalValues_match() {
+ assertThat(BrushBehavior.Source.NORMALIZED_PRESSURE.hashCode())
+ .isEqualTo(BrushBehavior.Source.NORMALIZED_PRESSURE.hashCode())
+
+ assertThat(BrushBehavior.Source.TILT_IN_RADIANS.hashCode())
+ .isNotEqualTo(BrushBehavior.Source.NORMALIZED_PRESSURE.hashCode())
+ }
+
+ @Test
+ fun sourceEquals_checksEqualityOfValues() {
+ assertThat(BrushBehavior.Source.NORMALIZED_PRESSURE)
+ .isEqualTo(BrushBehavior.Source.NORMALIZED_PRESSURE)
+
+ assertThat(BrushBehavior.Source.TILT_IN_RADIANS)
+ .isNotEqualTo(BrushBehavior.Source.NORMALIZED_PRESSURE)
+ assertThat(BrushBehavior.Source.TILT_IN_RADIANS).isNotEqualTo(null)
+ }
+
+ @Test
+ fun sourceToString_returnsCorrectString() {
+ assertThat(BrushBehavior.Source.CONSTANT_ZERO.toString())
+ .isEqualTo("BrushBehavior.Source.CONSTANT_ZERO")
+ assertThat(BrushBehavior.Source.NORMALIZED_PRESSURE.toString())
+ .isEqualTo("BrushBehavior.Source.NORMALIZED_PRESSURE")
+ assertThat(BrushBehavior.Source.TILT_IN_RADIANS.toString())
+ .isEqualTo("BrushBehavior.Source.TILT_IN_RADIANS")
+ assertThat(BrushBehavior.Source.TILT_X_IN_RADIANS.toString())
+ .isEqualTo("BrushBehavior.Source.TILT_X_IN_RADIANS")
+ assertThat(BrushBehavior.Source.TILT_Y_IN_RADIANS.toString())
+ .isEqualTo("BrushBehavior.Source.TILT_Y_IN_RADIANS")
+ assertThat(BrushBehavior.Source.ORIENTATION_IN_RADIANS.toString())
+ .isEqualTo("BrushBehavior.Source.ORIENTATION_IN_RADIANS")
+ assertThat(BrushBehavior.Source.ORIENTATION_ABOUT_ZERO_IN_RADIANS.toString())
+ .isEqualTo("BrushBehavior.Source.ORIENTATION_ABOUT_ZERO_IN_RADIANS")
+ assertThat(BrushBehavior.Source.SPEED_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND.toString())
+ .isEqualTo("BrushBehavior.Source.SPEED_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND")
+ assertThat(BrushBehavior.Source.VELOCITY_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND.toString())
+ .isEqualTo("BrushBehavior.Source.VELOCITY_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND")
+ assertThat(BrushBehavior.Source.VELOCITY_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND.toString())
+ .isEqualTo("BrushBehavior.Source.VELOCITY_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND")
+ assertThat(BrushBehavior.Source.DIRECTION_IN_RADIANS.toString())
+ .isEqualTo("BrushBehavior.Source.DIRECTION_IN_RADIANS")
+ assertThat(BrushBehavior.Source.DIRECTION_ABOUT_ZERO_IN_RADIANS.toString())
+ .isEqualTo("BrushBehavior.Source.DIRECTION_ABOUT_ZERO_IN_RADIANS")
+ assertThat(BrushBehavior.Source.NORMALIZED_DIRECTION_X.toString())
+ .isEqualTo("BrushBehavior.Source.NORMALIZED_DIRECTION_X")
+ assertThat(BrushBehavior.Source.NORMALIZED_DIRECTION_Y.toString())
+ .isEqualTo("BrushBehavior.Source.NORMALIZED_DIRECTION_Y")
+ assertThat(BrushBehavior.Source.DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE.toString())
+ .isEqualTo("BrushBehavior.Source.DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE")
+ assertThat(BrushBehavior.Source.TIME_OF_INPUT_IN_SECONDS.toString())
+ .isEqualTo("BrushBehavior.Source.TIME_OF_INPUT_IN_SECONDS")
+ assertThat(BrushBehavior.Source.TIME_OF_INPUT_IN_MILLIS.toString())
+ .isEqualTo("BrushBehavior.Source.TIME_OF_INPUT_IN_MILLIS")
+ assertThat(
+ BrushBehavior.Source.PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE
+ .toString()
+ )
+ .isEqualTo(
+ "BrushBehavior.Source.PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE"
+ )
+ assertThat(BrushBehavior.Source.PREDICTED_TIME_ELAPSED_IN_SECONDS.toString())
+ .isEqualTo("BrushBehavior.Source.PREDICTED_TIME_ELAPSED_IN_SECONDS")
+ assertThat(BrushBehavior.Source.PREDICTED_TIME_ELAPSED_IN_MILLIS.toString())
+ .isEqualTo("BrushBehavior.Source.PREDICTED_TIME_ELAPSED_IN_MILLIS")
+ assertThat(BrushBehavior.Source.DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE.toString())
+ .isEqualTo("BrushBehavior.Source.DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE")
+ assertThat(BrushBehavior.Source.TIME_SINCE_INPUT_IN_SECONDS.toString())
+ .isEqualTo("BrushBehavior.Source.TIME_SINCE_INPUT_IN_SECONDS")
+ assertThat(BrushBehavior.Source.TIME_SINCE_INPUT_IN_MILLIS.toString())
+ .isEqualTo("BrushBehavior.Source.TIME_SINCE_INPUT_IN_MILLIS")
+ assertThat(
+ BrushBehavior.Source.ACCELERATION_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED
+ .toString()
+ )
+ .isEqualTo(
+ "BrushBehavior.Source.ACCELERATION_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED"
+ )
+ assertThat(
+ BrushBehavior.Source.ACCELERATION_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED
+ .toString()
+ )
+ .isEqualTo(
+ "BrushBehavior.Source.ACCELERATION_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED"
+ )
+ assertThat(
+ BrushBehavior.Source.ACCELERATION_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED
+ .toString()
+ )
+ .isEqualTo(
+ "BrushBehavior.Source.ACCELERATION_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED"
+ )
+ assertThat(
+ BrushBehavior.Source
+ .ACCELERATION_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED
+ .toString()
+ )
+ .isEqualTo(
+ "BrushBehavior.Source.ACCELERATION_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED"
+ )
+ assertThat(
+ BrushBehavior.Source
+ .ACCELERATION_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED
+ .toString()
+ )
+ .isEqualTo(
+ "BrushBehavior.Source.ACCELERATION_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED"
+ )
+ assertThat(BrushBehavior.Source.INPUT_SPEED_IN_CENTIMETERS_PER_SECOND.toString())
+ .isEqualTo("BrushBehavior.Source.INPUT_SPEED_IN_CENTIMETERS_PER_SECOND")
+ assertThat(BrushBehavior.Source.INPUT_VELOCITY_X_IN_CENTIMETERS_PER_SECOND.toString())
+ .isEqualTo("BrushBehavior.Source.INPUT_VELOCITY_X_IN_CENTIMETERS_PER_SECOND")
+ assertThat(BrushBehavior.Source.INPUT_VELOCITY_Y_IN_CENTIMETERS_PER_SECOND.toString())
+ .isEqualTo("BrushBehavior.Source.INPUT_VELOCITY_Y_IN_CENTIMETERS_PER_SECOND")
+ assertThat(BrushBehavior.Source.INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS.toString())
+ .isEqualTo("BrushBehavior.Source.INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS")
+ assertThat(BrushBehavior.Source.PREDICTED_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS.toString())
+ .isEqualTo("BrushBehavior.Source.PREDICTED_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS")
+ assertThat(
+ BrushBehavior.Source.INPUT_ACCELERATION_IN_CENTIMETERS_PER_SECOND_SQUARED.toString()
+ )
+ .isEqualTo("BrushBehavior.Source.INPUT_ACCELERATION_IN_CENTIMETERS_PER_SECOND_SQUARED")
+ assertThat(
+ BrushBehavior.Source.INPUT_ACCELERATION_X_IN_CENTIMETERS_PER_SECOND_SQUARED
+ .toString()
+ )
+ .isEqualTo(
+ "BrushBehavior.Source.INPUT_ACCELERATION_X_IN_CENTIMETERS_PER_SECOND_SQUARED"
+ )
+ assertThat(
+ BrushBehavior.Source.INPUT_ACCELERATION_Y_IN_CENTIMETERS_PER_SECOND_SQUARED
+ .toString()
+ )
+ .isEqualTo(
+ "BrushBehavior.Source.INPUT_ACCELERATION_Y_IN_CENTIMETERS_PER_SECOND_SQUARED"
+ )
+ assertThat(
+ BrushBehavior.Source.INPUT_ACCELERATION_FORWARD_IN_CENTIMETERS_PER_SECOND_SQUARED
+ .toString()
+ )
+ .isEqualTo(
+ "BrushBehavior.Source.INPUT_ACCELERATION_FORWARD_IN_CENTIMETERS_PER_SECOND_SQUARED"
+ )
+ assertThat(
+ BrushBehavior.Source.INPUT_ACCELERATION_LATERAL_IN_CENTIMETERS_PER_SECOND_SQUARED
+ .toString()
+ )
+ .isEqualTo(
+ "BrushBehavior.Source.INPUT_ACCELERATION_LATERAL_IN_CENTIMETERS_PER_SECOND_SQUARED"
+ )
+ }
+
+ @Test
+ fun targetConstants_areDistinct() {
+ val list =
+ listOf<BrushBehavior.Target>(
+ BrushBehavior.Target.WIDTH_MULTIPLIER,
+ BrushBehavior.Target.HEIGHT_MULTIPLIER,
+ BrushBehavior.Target.SIZE_MULTIPLIER,
+ BrushBehavior.Target.SLANT_OFFSET_IN_RADIANS,
+ BrushBehavior.Target.PINCH_OFFSET,
+ BrushBehavior.Target.ROTATION_OFFSET_IN_RADIANS,
+ BrushBehavior.Target.CORNER_ROUNDING_OFFSET,
+ BrushBehavior.Target.POSITION_OFFSET_X_IN_MULTIPLES_OF_BRUSH_SIZE,
+ BrushBehavior.Target.POSITION_OFFSET_Y_IN_MULTIPLES_OF_BRUSH_SIZE,
+ BrushBehavior.Target.POSITION_OFFSET_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE,
+ BrushBehavior.Target.POSITION_OFFSET_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE,
+ BrushBehavior.Target.HUE_OFFSET_IN_RADIANS,
+ BrushBehavior.Target.SATURATION_MULTIPLIER,
+ BrushBehavior.Target.LUMINOSITY,
+ BrushBehavior.Target.OPACITY_MULTIPLIER,
+ )
+ assertThat(list.toSet()).hasSize(list.size)
+ }
+
+ @Test
+ fun targetHashCode_withIdenticalValues_match() {
+ assertThat(BrushBehavior.Target.WIDTH_MULTIPLIER.hashCode())
+ .isEqualTo(BrushBehavior.Target.WIDTH_MULTIPLIER.hashCode())
+
+ assertThat(BrushBehavior.Target.WIDTH_MULTIPLIER.hashCode())
+ .isNotEqualTo(BrushBehavior.Target.HEIGHT_MULTIPLIER.hashCode())
+ }
+
+ @Test
+ fun targetEquals_checksEqualityOfValues() {
+ assertThat(BrushBehavior.Target.WIDTH_MULTIPLIER)
+ .isEqualTo(BrushBehavior.Target.WIDTH_MULTIPLIER)
+
+ assertThat(BrushBehavior.Target.WIDTH_MULTIPLIER)
+ .isNotEqualTo(BrushBehavior.Target.HEIGHT_MULTIPLIER)
+ assertThat(BrushBehavior.Target.WIDTH_MULTIPLIER).isNotEqualTo(null)
+ }
+
+ @Test
+ fun targetToString_returnsCorrectString() {
+ assertThat(BrushBehavior.Target.WIDTH_MULTIPLIER.toString())
+ .isEqualTo("BrushBehavior.Target.WIDTH_MULTIPLIER")
+ assertThat(BrushBehavior.Target.HEIGHT_MULTIPLIER.toString())
+ .isEqualTo("BrushBehavior.Target.HEIGHT_MULTIPLIER")
+ assertThat(BrushBehavior.Target.SIZE_MULTIPLIER.toString())
+ .isEqualTo("BrushBehavior.Target.SIZE_MULTIPLIER")
+ assertThat(BrushBehavior.Target.SLANT_OFFSET_IN_RADIANS.toString())
+ .isEqualTo("BrushBehavior.Target.SLANT_OFFSET_IN_RADIANS")
+ assertThat(BrushBehavior.Target.PINCH_OFFSET.toString())
+ .isEqualTo("BrushBehavior.Target.PINCH_OFFSET")
+ assertThat(BrushBehavior.Target.ROTATION_OFFSET_IN_RADIANS.toString())
+ .isEqualTo("BrushBehavior.Target.ROTATION_OFFSET_IN_RADIANS")
+ assertThat(BrushBehavior.Target.CORNER_ROUNDING_OFFSET.toString())
+ .isEqualTo("BrushBehavior.Target.CORNER_ROUNDING_OFFSET")
+ assertThat(BrushBehavior.Target.POSITION_OFFSET_X_IN_MULTIPLES_OF_BRUSH_SIZE.toString())
+ .isEqualTo("BrushBehavior.Target.POSITION_OFFSET_X_IN_MULTIPLES_OF_BRUSH_SIZE")
+ assertThat(BrushBehavior.Target.POSITION_OFFSET_Y_IN_MULTIPLES_OF_BRUSH_SIZE.toString())
+ .isEqualTo("BrushBehavior.Target.POSITION_OFFSET_Y_IN_MULTIPLES_OF_BRUSH_SIZE")
+ assertThat(
+ BrushBehavior.Target.POSITION_OFFSET_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE.toString()
+ )
+ .isEqualTo("BrushBehavior.Target.POSITION_OFFSET_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE")
+ assertThat(
+ BrushBehavior.Target.POSITION_OFFSET_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE.toString()
+ )
+ .isEqualTo("BrushBehavior.Target.POSITION_OFFSET_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE")
+ assertThat(BrushBehavior.Target.HUE_OFFSET_IN_RADIANS.toString())
+ .isEqualTo("BrushBehavior.Target.HUE_OFFSET_IN_RADIANS")
+ assertThat(BrushBehavior.Target.SATURATION_MULTIPLIER.toString())
+ .isEqualTo("BrushBehavior.Target.SATURATION_MULTIPLIER")
+ assertThat(BrushBehavior.Target.LUMINOSITY.toString())
+ .isEqualTo("BrushBehavior.Target.LUMINOSITY")
+ assertThat(BrushBehavior.Target.OPACITY_MULTIPLIER.toString())
+ .isEqualTo("BrushBehavior.Target.OPACITY_MULTIPLIER")
+ }
+
+ @Test
+ fun outOfRangeConstants_areDistinct() {
+ val list =
+ listOf<BrushBehavior.OutOfRange>(
+ BrushBehavior.OutOfRange.CLAMP,
+ BrushBehavior.OutOfRange.REPEAT,
+ BrushBehavior.OutOfRange.MIRROR,
+ )
+ assertThat(list.toSet()).hasSize(list.size)
+ }
+
+ @Test
+ fun outOfRangeHashCode_withIdenticalValues_match() {
+ assertThat(BrushBehavior.OutOfRange.CLAMP.hashCode())
+ .isEqualTo(BrushBehavior.OutOfRange.CLAMP.hashCode())
+
+ assertThat(BrushBehavior.OutOfRange.CLAMP.hashCode())
+ .isNotEqualTo(BrushBehavior.OutOfRange.REPEAT.hashCode())
+ }
+
+ @Test
+ fun outOfRangeEquals_checksEqualityOfValues() {
+ assertThat(BrushBehavior.OutOfRange.CLAMP).isEqualTo(BrushBehavior.OutOfRange.CLAMP)
+
+ assertThat(BrushBehavior.OutOfRange.CLAMP).isNotEqualTo(BrushBehavior.OutOfRange.REPEAT)
+ assertThat(BrushBehavior.OutOfRange.CLAMP).isNotEqualTo(null)
+ }
+
+ @Test
+ fun outOfRangeToString_returnsCorrectString() {
+ assertThat(BrushBehavior.OutOfRange.CLAMP.toString())
+ .isEqualTo("BrushBehavior.OutOfRange.CLAMP")
+ assertThat(BrushBehavior.OutOfRange.REPEAT.toString())
+ .isEqualTo("BrushBehavior.OutOfRange.REPEAT")
+ assertThat(BrushBehavior.OutOfRange.MIRROR.toString())
+ .isEqualTo("BrushBehavior.OutOfRange.MIRROR")
+ }
+
+ @Test
+ fun optionalInputPropertyConstants_areDistinct() {
+ val list =
+ listOf<BrushBehavior.OptionalInputProperty>(
+ BrushBehavior.OptionalInputProperty.PRESSURE,
+ BrushBehavior.OptionalInputProperty.TILT,
+ BrushBehavior.OptionalInputProperty.ORIENTATION,
+ BrushBehavior.OptionalInputProperty.TILT_X_AND_Y,
+ )
+ assertThat(list.toSet()).hasSize(list.size)
+ }
+
+ @Test
+ fun optionalInputPropertyHashCode_withIdenticalValues_match() {
+ assertThat(BrushBehavior.OptionalInputProperty.PRESSURE.hashCode())
+ .isEqualTo(BrushBehavior.OptionalInputProperty.PRESSURE.hashCode())
+
+ assertThat(BrushBehavior.OptionalInputProperty.PRESSURE.hashCode())
+ .isNotEqualTo(BrushBehavior.OptionalInputProperty.TILT.hashCode())
+ }
+
+ @Test
+ fun optionalInputPropertyEquals_checksEqualityOfValues() {
+ assertThat(BrushBehavior.OptionalInputProperty.PRESSURE)
+ .isEqualTo(BrushBehavior.OptionalInputProperty.PRESSURE)
+
+ assertThat(BrushBehavior.OptionalInputProperty.PRESSURE)
+ .isNotEqualTo(BrushBehavior.OptionalInputProperty.TILT)
+ assertThat(BrushBehavior.OptionalInputProperty.PRESSURE).isNotEqualTo(null)
+ }
+
+ @Test
+ fun optionalInputPropertyToString_returnsCorrectString() {
+ assertThat(BrushBehavior.OptionalInputProperty.PRESSURE.toString())
+ .isEqualTo("BrushBehavior.OptionalInputProperty.PRESSURE")
+ assertThat(BrushBehavior.OptionalInputProperty.TILT.toString())
+ .isEqualTo("BrushBehavior.OptionalInputProperty.TILT")
+ assertThat(BrushBehavior.OptionalInputProperty.ORIENTATION.toString())
+ .isEqualTo("BrushBehavior.OptionalInputProperty.ORIENTATION")
+ assertThat(BrushBehavior.OptionalInputProperty.TILT_X_AND_Y.toString())
+ .isEqualTo("BrushBehavior.OptionalInputProperty.TILT_X_AND_Y")
+ }
+
+ @Test
+ fun binaryOpConstants_areDistinct() {
+ val list =
+ listOf<BrushBehavior.BinaryOp>(
+ BrushBehavior.BinaryOp.PRODUCT,
+ BrushBehavior.BinaryOp.SUM
+ )
+ assertThat(list.toSet()).hasSize(list.size)
+ }
+
+ @Test
+ fun binaryOpHashCode_withIdenticalValues_match() {
+ assertThat(BrushBehavior.BinaryOp.PRODUCT.hashCode())
+ .isEqualTo(BrushBehavior.BinaryOp.PRODUCT.hashCode())
+
+ assertThat(BrushBehavior.BinaryOp.PRODUCT.hashCode())
+ .isNotEqualTo(BrushBehavior.BinaryOp.SUM.hashCode())
+ }
+
+ @Test
+ fun binaryOpEquals_checksEqualityOfValues() {
+ assertThat(BrushBehavior.BinaryOp.PRODUCT).isEqualTo(BrushBehavior.BinaryOp.PRODUCT)
+
+ assertThat(BrushBehavior.BinaryOp.PRODUCT).isNotEqualTo(BrushBehavior.BinaryOp.SUM)
+ assertThat(BrushBehavior.BinaryOp.PRODUCT).isNotEqualTo(null)
+ }
+
+ @Test
+ fun binaryOpToString_returnsCorrectString() {
+ assertThat(BrushBehavior.BinaryOp.PRODUCT.toString())
+ .isEqualTo("BrushBehavior.BinaryOp.PRODUCT")
+ assertThat(BrushBehavior.BinaryOp.SUM.toString()).isEqualTo("BrushBehavior.BinaryOp.SUM")
+ }
+
+ @Test
+ fun dampingSourceConstants_areDistinct() {
+ val list = listOf<BrushBehavior.DampingSource>(BrushBehavior.DampingSource.TIME_IN_SECONDS)
+ assertThat(list.toSet()).hasSize(list.size)
+ }
+
+ @Test
+ fun dampingSourceHashCode_withIdenticalValues_match() {
+ assertThat(BrushBehavior.DampingSource.TIME_IN_SECONDS.hashCode())
+ .isEqualTo(BrushBehavior.DampingSource.TIME_IN_SECONDS.hashCode())
+ }
+
+ @Test
+ fun dampingSourceEquals_checksEqualityOfValues() {
+ assertThat(BrushBehavior.DampingSource.TIME_IN_SECONDS)
+ .isEqualTo(BrushBehavior.DampingSource.TIME_IN_SECONDS)
+
+ assertThat(BrushBehavior.DampingSource.TIME_IN_SECONDS).isNotEqualTo(null)
+ }
+
+ @Test
+ fun dampingSourceToString_returnsCorrectString() {
+ assertThat(BrushBehavior.DampingSource.TIME_IN_SECONDS.toString())
+ .isEqualTo("BrushBehavior.DampingSource.TIME_IN_SECONDS")
+ }
+
+ @Test
+ fun sourceNodeConstructor_throwsForNonFiniteSourceValueRange() {
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior.SourceNode(BrushBehavior.Source.NORMALIZED_PRESSURE, Float.NaN, 1f)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior.SourceNode(
+ BrushBehavior.Source.NORMALIZED_PRESSURE,
+ 0f,
+ Float.POSITIVE_INFINITY,
+ )
+ }
+ }
+
+ @Test
+ fun sourceNodeConstructor_throwsForEmptySourceValueRange() {
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior.SourceNode(BrushBehavior.Source.NORMALIZED_PRESSURE, 0.5f, 0.5f)
+ }
+ }
+
+ @Test
+ fun sourceNodeInputs_isEmpty() {
+ val node = BrushBehavior.SourceNode(BrushBehavior.Source.NORMALIZED_PRESSURE, 0f, 1f)
+ assertThat(node.inputs()).isEmpty()
+ }
+
+ @Test
+ fun sourceNodeToString() {
+ val node = BrushBehavior.SourceNode(BrushBehavior.Source.NORMALIZED_PRESSURE, 0f, 1f)
+ assertThat(node.toString()).isEqualTo("SourceNode(NORMALIZED_PRESSURE, 0.0, 1.0, CLAMP)")
+ }
+
+ @Test
+ fun sourceNodeEquals_checksEqualityOfValues() {
+ val node1 = BrushBehavior.SourceNode(BrushBehavior.Source.NORMALIZED_PRESSURE, 0f, 1f)
+ val node2 = BrushBehavior.SourceNode(BrushBehavior.Source.NORMALIZED_PRESSURE, 0f, 1f)
+ val node3 = BrushBehavior.SourceNode(BrushBehavior.Source.NORMALIZED_PRESSURE, 0f, 2f)
+ assertThat(node1).isEqualTo(node2)
+ assertThat(node1).isNotEqualTo(node3)
+ }
+
+ @Test
+ fun sourceNodeHashCode_withIdenticalValues_match() {
+ val node1 = BrushBehavior.SourceNode(BrushBehavior.Source.NORMALIZED_PRESSURE, 0f, 1f)
+ val node2 = BrushBehavior.SourceNode(BrushBehavior.Source.NORMALIZED_PRESSURE, 0f, 1f)
+ val node3 = BrushBehavior.SourceNode(BrushBehavior.Source.NORMALIZED_PRESSURE, 0f, 2f)
+ assertThat(node1.hashCode()).isEqualTo(node2.hashCode())
+ assertThat(node1.hashCode()).isNotEqualTo(node3.hashCode())
+ }
+
+ @Test
+ fun constantNodeConstructor_throwsForNonFiniteValue() {
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior.ConstantNode(Float.POSITIVE_INFINITY)
+ }
+ assertFailsWith<IllegalArgumentException> { BrushBehavior.ConstantNode(Float.NaN) }
+ }
+
+ @Test
+ fun constantNodeInputs_isEmpty() {
+ assertThat(BrushBehavior.ConstantNode(42f).inputs()).isEmpty()
+ }
+
+ @Test
+ fun constantNodeToString() {
+ assertThat(BrushBehavior.ConstantNode(42f).toString()).isEqualTo("ConstantNode(42.0)")
+ }
+
+ @Test
+ fun constantNodeEquals_checksEqualityOfValues() {
+ val node1 = BrushBehavior.ConstantNode(1f)
+ val node2 = BrushBehavior.ConstantNode(1f)
+ val node3 = BrushBehavior.ConstantNode(2f)
+ assertThat(node1).isEqualTo(node2)
+ assertThat(node1).isNotEqualTo(node3)
+ }
+
+ @Test
+ fun constantNodeHashCode_withIdenticalValues_match() {
+ val node1 = BrushBehavior.ConstantNode(1f)
+ val node2 = BrushBehavior.ConstantNode(1f)
+ val node3 = BrushBehavior.ConstantNode(2f)
+ assertThat(node1.hashCode()).isEqualTo(node2.hashCode())
+ assertThat(node1.hashCode()).isNotEqualTo(node3.hashCode())
+ }
+
+ @Test
+ fun fallbackFilterNodeInputs_containsInput() {
+ val input = BrushBehavior.ConstantNode(0f)
+ val node =
+ BrushBehavior.FallbackFilterNode(BrushBehavior.OptionalInputProperty.PRESSURE, input)
+ assertThat(node.inputs()).containsExactly(input)
+ }
+
+ @Test
+ fun fallbackFilterNodeToString() {
+ val input = BrushBehavior.ConstantNode(0f)
+ val node =
+ BrushBehavior.FallbackFilterNode(BrushBehavior.OptionalInputProperty.PRESSURE, input)
+ assertThat(node.toString()).isEqualTo("FallbackFilterNode(PRESSURE, ConstantNode(0.0))")
+ }
+
+ @Test
+ fun fallbackFilterNodeEquals_checksEqualityOfValues() {
+ val node1 =
+ BrushBehavior.FallbackFilterNode(
+ BrushBehavior.OptionalInputProperty.PRESSURE,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node2 =
+ BrushBehavior.FallbackFilterNode(
+ BrushBehavior.OptionalInputProperty.PRESSURE,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node3 =
+ BrushBehavior.FallbackFilterNode(
+ BrushBehavior.OptionalInputProperty.PRESSURE,
+ BrushBehavior.ConstantNode(2f),
+ )
+ assertThat(node1).isEqualTo(node2)
+ assertThat(node1).isNotEqualTo(node3)
+ }
+
+ @Test
+ fun fallbackFilterNodeHashCode_withIdenticalValues_match() {
+ val node1 =
+ BrushBehavior.FallbackFilterNode(
+ BrushBehavior.OptionalInputProperty.PRESSURE,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node2 =
+ BrushBehavior.FallbackFilterNode(
+ BrushBehavior.OptionalInputProperty.PRESSURE,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node3 =
+ BrushBehavior.FallbackFilterNode(
+ BrushBehavior.OptionalInputProperty.PRESSURE,
+ BrushBehavior.ConstantNode(2f),
+ )
+ assertThat(node1.hashCode()).isEqualTo(node2.hashCode())
+ assertThat(node1.hashCode()).isNotEqualTo(node3.hashCode())
+ }
+
+ @Test
+ fun toolTypeFilterNodeConstructor_throwsForEmptyEnabledToolTypes() {
+ val input = BrushBehavior.ConstantNode(0f)
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior.ToolTypeFilterNode(emptySet(), input)
+ }
+ }
+
+ @Test
+ fun toolTypeFilterNodeInputs_containsInput() {
+ val input = BrushBehavior.ConstantNode(0f)
+ val node = BrushBehavior.ToolTypeFilterNode(setOf(InputToolType.STYLUS), input)
+ assertThat(node.inputs()).containsExactly(input)
+ }
+
+ @Test
+ fun toolTypeFilterNodeToString() {
+ val input = BrushBehavior.ConstantNode(0f)
+ val node = BrushBehavior.ToolTypeFilterNode(setOf(InputToolType.STYLUS), input)
+ assertThat(node.toString())
+ .isEqualTo("ToolTypeFilterNode([InputToolType.STYLUS], ConstantNode(0.0))")
+ }
+
+ @Test
+ fun toolTypeFilterNodeEquals_checksEqualityOfValues() {
+ val node1 =
+ BrushBehavior.ToolTypeFilterNode(
+ setOf(InputToolType.STYLUS),
+ BrushBehavior.ConstantNode(1f)
+ )
+ val node2 =
+ BrushBehavior.ToolTypeFilterNode(
+ setOf(InputToolType.STYLUS),
+ BrushBehavior.ConstantNode(1f)
+ )
+ val node3 =
+ BrushBehavior.ToolTypeFilterNode(
+ setOf(InputToolType.STYLUS),
+ BrushBehavior.ConstantNode(2f)
+ )
+ assertThat(node1).isEqualTo(node2)
+ assertThat(node1).isNotEqualTo(node3)
+ }
+
+ @Test
+ fun toolTypeFilterNodeHashCode_withIdenticalValues_match() {
+ val node1 =
+ BrushBehavior.ToolTypeFilterNode(
+ setOf(InputToolType.STYLUS),
+ BrushBehavior.ConstantNode(1f)
+ )
+ val node2 =
+ BrushBehavior.ToolTypeFilterNode(
+ setOf(InputToolType.STYLUS),
+ BrushBehavior.ConstantNode(1f)
+ )
+ val node3 =
+ BrushBehavior.ToolTypeFilterNode(
+ setOf(InputToolType.STYLUS),
+ BrushBehavior.ConstantNode(2f)
+ )
+ assertThat(node1.hashCode()).isEqualTo(node2.hashCode())
+ assertThat(node1.hashCode()).isNotEqualTo(node3.hashCode())
+ }
+
+ @Test
+ fun dampingNodeConstructor_throwsForNonFiniteDampingGap() {
+ val input = BrushBehavior.ConstantNode(0f)
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior.DampingNode(
+ BrushBehavior.DampingSource.TIME_IN_SECONDS,
+ Float.POSITIVE_INFINITY,
+ input,
+ )
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior.DampingNode(BrushBehavior.DampingSource.TIME_IN_SECONDS, Float.NaN, input)
+ }
+ }
+
+ @Test
+ fun dampingNodeConstructor_throwsForNegativeDampingGap() {
+ val input = BrushBehavior.ConstantNode(0f)
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior.DampingNode(BrushBehavior.DampingSource.TIME_IN_SECONDS, -1f, input)
+ }
+ }
+
+ @Test
+ fun dampingNodeInputs_containsInput() {
+ val input = BrushBehavior.ConstantNode(0f)
+ val node = BrushBehavior.DampingNode(BrushBehavior.DampingSource.TIME_IN_SECONDS, 1f, input)
+ assertThat(node.inputs()).containsExactly(input)
+ }
+
+ @Test
+ fun dampingNodeToString() {
+ val input = BrushBehavior.ConstantNode(0f)
+ val node = BrushBehavior.DampingNode(BrushBehavior.DampingSource.TIME_IN_SECONDS, 1f, input)
+ assertThat(node.toString())
+ .isEqualTo("DampingNode(TIME_IN_SECONDS, 1.0, ConstantNode(0.0))")
+ }
+
+ @Test
+ fun dampingNodeEquals_checksEqualityOfValues() {
+ val node1 =
+ BrushBehavior.DampingNode(
+ BrushBehavior.DampingSource.TIME_IN_SECONDS,
+ 1f,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node2 =
+ BrushBehavior.DampingNode(
+ BrushBehavior.DampingSource.TIME_IN_SECONDS,
+ 1f,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node3 =
+ BrushBehavior.DampingNode(
+ BrushBehavior.DampingSource.TIME_IN_SECONDS,
+ 1f,
+ BrushBehavior.ConstantNode(2f),
+ )
+ assertThat(node1).isEqualTo(node2)
+ assertThat(node1).isNotEqualTo(node3)
+ }
+
+ @Test
+ fun dampingNodeHashCode_withIdenticalValues_match() {
+ val node1 =
+ BrushBehavior.DampingNode(
+ BrushBehavior.DampingSource.TIME_IN_SECONDS,
+ 1f,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node2 =
+ BrushBehavior.DampingNode(
+ BrushBehavior.DampingSource.TIME_IN_SECONDS,
+ 1f,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node3 =
+ BrushBehavior.DampingNode(
+ BrushBehavior.DampingSource.TIME_IN_SECONDS,
+ 1f,
+ BrushBehavior.ConstantNode(2f),
+ )
+ assertThat(node1.hashCode()).isEqualTo(node2.hashCode())
+ assertThat(node1.hashCode()).isNotEqualTo(node3.hashCode())
+ }
+
+ @Test
+ fun responseNodeInputs_containsInput() {
+ val input = BrushBehavior.ConstantNode(0f)
+ val node = BrushBehavior.ResponseNode(EasingFunction.Predefined.EASE, input)
+ assertThat(node.inputs()).containsExactly(input)
+ }
+
+ @Test
+ fun responseNodeToString() {
+ val input = BrushBehavior.ConstantNode(0f)
+ val node = BrushBehavior.ResponseNode(EasingFunction.Predefined.EASE, input)
+ assertThat(node.toString())
+ .isEqualTo("ResponseNode(EasingFunction.Predefined.EASE, ConstantNode(0.0))")
+ }
+
+ @Test
+ fun responseNodeEquals_checksEqualityOfValues() {
+ val node1 =
+ BrushBehavior.ResponseNode(
+ EasingFunction.Predefined.EASE,
+ BrushBehavior.ConstantNode(1f)
+ )
+ val node2 =
+ BrushBehavior.ResponseNode(
+ EasingFunction.Predefined.EASE,
+ BrushBehavior.ConstantNode(1f)
+ )
+ val node3 =
+ BrushBehavior.ResponseNode(
+ EasingFunction.Predefined.EASE,
+ BrushBehavior.ConstantNode(2f)
+ )
+ assertThat(node1).isEqualTo(node2)
+ assertThat(node1).isNotEqualTo(node3)
+ }
+
+ @Test
+ fun responseNodeHashCode_withIdenticalValues_match() {
+ val node1 =
+ BrushBehavior.ResponseNode(
+ EasingFunction.Predefined.EASE,
+ BrushBehavior.ConstantNode(1f)
+ )
+ val node2 =
+ BrushBehavior.ResponseNode(
+ EasingFunction.Predefined.EASE,
+ BrushBehavior.ConstantNode(1f)
+ )
+ val node3 =
+ BrushBehavior.ResponseNode(
+ EasingFunction.Predefined.EASE,
+ BrushBehavior.ConstantNode(2f)
+ )
+ assertThat(node1.hashCode()).isEqualTo(node2.hashCode())
+ assertThat(node1.hashCode()).isNotEqualTo(node3.hashCode())
+ }
+
+ @Test
+ fun binaryOpNodeInputs_containsInputsInOrder() {
+ val firstInput = BrushBehavior.ConstantNode(0f)
+ val secondInput = BrushBehavior.ConstantNode(1f)
+ val node = BrushBehavior.BinaryOpNode(BrushBehavior.BinaryOp.SUM, firstInput, secondInput)
+ assertThat(node.inputs()).containsExactly(firstInput, secondInput).inOrder()
+ }
+
+ @Test
+ fun binaryOpNodeToString() {
+ val firstInput = BrushBehavior.ConstantNode(0f)
+ val secondInput = BrushBehavior.ConstantNode(1f)
+ val node = BrushBehavior.BinaryOpNode(BrushBehavior.BinaryOp.SUM, firstInput, secondInput)
+ assertThat(node.toString())
+ .isEqualTo("BinaryOpNode(SUM, ConstantNode(0.0), ConstantNode(1.0))")
+ }
+
+ @Test
+ fun binaryOpNodeEquals_checksEqualityOfValues() {
+ val node1 =
+ BrushBehavior.BinaryOpNode(
+ BrushBehavior.BinaryOp.SUM,
+ BrushBehavior.ConstantNode(0f),
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node2 =
+ BrushBehavior.BinaryOpNode(
+ BrushBehavior.BinaryOp.SUM,
+ BrushBehavior.ConstantNode(0f),
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node3 =
+ BrushBehavior.BinaryOpNode(
+ BrushBehavior.BinaryOp.SUM,
+ BrushBehavior.ConstantNode(0f),
+ BrushBehavior.ConstantNode(2f),
+ )
+ assertThat(node1).isEqualTo(node2)
+ assertThat(node1).isNotEqualTo(node3)
+ }
+
+ @Test
+ fun binaryOpNodeHashCode_withIdenticalValues_match() {
+ val node1 =
+ BrushBehavior.BinaryOpNode(
+ BrushBehavior.BinaryOp.SUM,
+ BrushBehavior.ConstantNode(0f),
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node2 =
+ BrushBehavior.BinaryOpNode(
+ BrushBehavior.BinaryOp.SUM,
+ BrushBehavior.ConstantNode(0f),
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node3 =
+ BrushBehavior.BinaryOpNode(
+ BrushBehavior.BinaryOp.SUM,
+ BrushBehavior.ConstantNode(0f),
+ BrushBehavior.ConstantNode(2f),
+ )
+ assertThat(node1.hashCode()).isEqualTo(node2.hashCode())
+ assertThat(node1.hashCode()).isNotEqualTo(node3.hashCode())
+ }
+
+ @Test
+ fun targetNodeConstructor_throwsForNonFiniteTargetModifierRange() {
+ val input = BrushBehavior.ConstantNode(0f)
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior.TargetNode(BrushBehavior.Target.SIZE_MULTIPLIER, Float.NaN, 1f, input)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior.TargetNode(
+ BrushBehavior.Target.SIZE_MULTIPLIER,
+ 0f,
+ Float.POSITIVE_INFINITY,
+ input,
+ )
+ }
+ }
+
+ @Test
+ fun targetNodeConstructor_throwsForEmptyTargetModifierRange() {
+ val input = BrushBehavior.ConstantNode(0f)
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior.TargetNode(BrushBehavior.Target.SIZE_MULTIPLIER, 0.5f, 0.5f, input)
+ }
+ }
+
+ @Test
+ fun targetNodeInputs_containsInput() {
+ val input = BrushBehavior.ConstantNode(0f)
+ val node = BrushBehavior.TargetNode(BrushBehavior.Target.SIZE_MULTIPLIER, 0f, 1f, input)
+ assertThat(node.inputs()).containsExactly(input)
+ }
+
+ @Test
+ fun targetNodeToString() {
+ val input = BrushBehavior.ConstantNode(0f)
+ val node = BrushBehavior.TargetNode(BrushBehavior.Target.SIZE_MULTIPLIER, 0f, 1f, input)
+ assertThat(node.toString())
+ .isEqualTo("TargetNode(SIZE_MULTIPLIER, 0.0, 1.0, ConstantNode(0.0))")
+ }
+
+ @Test
+ fun targetNodeEquals_checksEqualityOfValues() {
+ val node1 =
+ BrushBehavior.TargetNode(
+ BrushBehavior.Target.SIZE_MULTIPLIER,
+ 0f,
+ 1f,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node2 =
+ BrushBehavior.TargetNode(
+ BrushBehavior.Target.SIZE_MULTIPLIER,
+ 0f,
+ 1f,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node3 =
+ BrushBehavior.TargetNode(
+ BrushBehavior.Target.SIZE_MULTIPLIER,
+ 0f,
+ 1f,
+ BrushBehavior.ConstantNode(2f),
+ )
+ assertThat(node1).isEqualTo(node2)
+ assertThat(node1).isNotEqualTo(node3)
+ }
+
+ @Test
+ fun targetNodeHashCode_withIdenticalValues_match() {
+ val node1 =
+ BrushBehavior.TargetNode(
+ BrushBehavior.Target.SIZE_MULTIPLIER,
+ 0f,
+ 1f,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node2 =
+ BrushBehavior.TargetNode(
+ BrushBehavior.Target.SIZE_MULTIPLIER,
+ 0f,
+ 1f,
+ BrushBehavior.ConstantNode(1f),
+ )
+ val node3 =
+ BrushBehavior.TargetNode(
+ BrushBehavior.Target.SIZE_MULTIPLIER,
+ 0f,
+ 1f,
+ BrushBehavior.ConstantNode(2f),
+ )
+ assertThat(node1.hashCode()).isEqualTo(node2.hashCode())
+ assertThat(node1.hashCode()).isNotEqualTo(node3.hashCode())
+ }
+
+ @Test
+ fun brushBehaviorConstructor_withInvalidArguments_throws() {
+ // sourceValueRangeLowerBound not finite
+ val sourceValueRangeLowerBoundError =
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = Float.NaN, // Not finite.
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ }
+ assertThat(sourceValueRangeLowerBoundError.message).contains("source")
+ assertThat(sourceValueRangeLowerBoundError.message).contains("finite")
+
+ // sourceValueRangeUpperBound not finite
+ val sourceValueRangeUpperBoundError =
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 1.0f,
+ sourceValueRangeUpperBound = Float.NaN, // Not finite.
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ }
+ assertThat(sourceValueRangeUpperBoundError.message).contains("source")
+ assertThat(sourceValueRangeUpperBoundError.message).contains("finite")
+
+ // sourceValueRangeUpperBound == sourceValueRangeUpperBound
+ val sourceValueRangeError =
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.5f, // same as upper bound.
+ sourceValueRangeUpperBound = 0.5f, // same as lower bound.
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ }
+ assertThat(sourceValueRangeError.message).contains("source")
+ assertThat(sourceValueRangeError.message).contains("distinct")
+
+ // targetModifierRangeLowerBound not finite
+ val targetModifierRangeLowerBoundError =
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = Float.NaN, // Not finite.
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ }
+ assertThat(targetModifierRangeLowerBoundError.message).contains("target")
+ assertThat(targetModifierRangeLowerBoundError.message).contains("finite")
+
+ // targetModifierRangeUpperBound not finite
+ val targetModifierRangeUpperBoundError =
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = Float.NaN, // Not finite.
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ }
+ assertThat(targetModifierRangeUpperBoundError.message).contains("target")
+ assertThat(targetModifierRangeUpperBoundError.message).contains("finite")
+
+ // responseTimeMillis less than 0L
+ val responseTimeMillisError =
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = -1L, // Less than 0.
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ }
+ assertThat(responseTimeMillisError.message).contains("dampingGap")
+ assertThat(responseTimeMillisError.message).contains("non-negative")
+
+ // enabledToolType contains empty set.
+ val enabledToolTypeError =
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(),
+ )
+ }
+ assertThat(enabledToolTypeError.message).contains("enabledToolTypes")
+ assertThat(enabledToolTypeError.message).contains("non-empty")
+
+ // source and outOfRangeBehavior combination is invalid (TIME_SINCE_INPUT must use CLAMP)
+ val sourceOutOfRangeBehaviorError =
+ assertFailsWith<IllegalArgumentException> {
+ BrushBehavior(
+ source = BrushBehavior.Source.TIME_SINCE_INPUT_IN_SECONDS,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.REPEAT,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ }
+ assertThat(sourceOutOfRangeBehaviorError.message).contains("TimeSince")
+ assertThat(sourceOutOfRangeBehaviorError.message).contains("kClamp")
+ }
+
+ @Test
+ fun brushBehaviorCopy_withArguments_createsCopyWithChanges() {
+ val behavior1 =
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = 1.1f,
+ targetModifierRangeUpperBound = 1.7f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ isFallbackFor = BrushBehavior.OptionalInputProperty.TILT_X_AND_Y,
+ )
+ assertThat(behavior1.copy(responseTimeMillis = 3L))
+ .isEqualTo(
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = 1.1f,
+ targetModifierRangeUpperBound = 1.7f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 3L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ isFallbackFor = BrushBehavior.OptionalInputProperty.TILT_X_AND_Y,
+ )
+ )
+ }
+
+ @Test
+ fun brushBehaviorCopy_createsCopy() {
+ val behavior1 =
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = 1.1f,
+ targetModifierRangeUpperBound = 1.7f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ isFallbackFor = BrushBehavior.OptionalInputProperty.TILT_X_AND_Y,
+ )
+ val behavior2 = behavior1.copy()
+ assertThat(behavior2).isEqualTo(behavior1)
+ assertThat(behavior2).isNotSameInstanceAs(behavior1)
+ }
+
+ @Test
+ fun brushBehaviorToString_returnsReasonableString() {
+ assertThat(
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ .toString()
+ )
+ .isEqualTo(
+ "BrushBehavior(source=BrushBehavior.Source.NORMALIZED_PRESSURE, " +
+ "target=BrushBehavior.Target.WIDTH_MULTIPLIER, " +
+ "sourceOutOfRangeBehavior=BrushBehavior.OutOfRange.CLAMP, " +
+ "sourceValueRangeLowerBound=0.0, sourceValueRangeUpperBound=1.0, " +
+ "targetModifierRangeLowerBound=1.0, targetModifierRangeUpperBound=1.75, " +
+ "responseCurve=EasingFunction.Predefined.EASE_IN_OUT, responseTimeMillis=1, " +
+ "enabledToolTypes=[InputToolType.STYLUS], isFallbackFor=null)"
+ )
+ }
+
+ @Test
+ fun brushBehaviorEquals_withIdenticalValues_returnsTrue() {
+ val original =
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+
+ val exact =
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+
+ assertThat(original.equals(exact)).isTrue()
+ }
+
+ @Test
+ fun brushBehaviorEquals_withDifferentValues_returnsFalse() {
+ val original =
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+
+ assertThat(
+ original.equals(
+ BrushBehavior(
+ source = BrushBehavior.Source.TILT_IN_RADIANS, // different
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ )
+ )
+ .isFalse()
+ assertThat(
+ original.equals(
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.HEIGHT_MULTIPLIER, // different
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ )
+ )
+ .isFalse()
+
+ assertThat(
+ original.equals(
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.REPEAT, // different
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ )
+ )
+ .isFalse()
+ assertThat(
+ original.equals(
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.3f, // different
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ )
+ )
+ .isFalse()
+ assertThat(
+ original.equals(
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 0.8f, // different
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ )
+ )
+ .isFalse()
+ assertThat(
+ original.equals(
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.56f, // different
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ )
+ )
+ .isFalse()
+ assertThat(
+ original.equals(
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.99f, // different
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ )
+ )
+ .isFalse()
+ assertThat(
+ original.equals(
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.LINEAR, // different
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ )
+ )
+ .isFalse()
+ assertThat(
+ original.equals(
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 35L, // different
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ )
+ )
+ )
+ .isFalse()
+ assertThat(
+ original.equals(
+ BrushBehavior(
+ source = BrushBehavior.Source.NORMALIZED_PRESSURE,
+ target = BrushBehavior.Target.WIDTH_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.0f,
+ sourceValueRangeUpperBound = 1.0f,
+ targetModifierRangeLowerBound = 1.0f,
+ targetModifierRangeUpperBound = 1.75f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.CLAMP,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.TOUCH), // different
+ )
+ )
+ )
+ .isFalse()
+ }
+
+ /**
+ * Creates an expected C++ StepFunction BrushBehavior and returns true if every property of the
+ * Kotlin BrushBehavior's JNI-created C++ counterpart is equivalent to the expected C++
+ * BrushBehavior.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun matchesNativeStepBehavior(
+ nativePointerToActualBrushBehavior: Long
+ ): Boolean
+
+ /**
+ * Creates an expected C++ PredefinedFunction BrushBehavior and returns true if every property
+ * of the Kotlin BrushBehavior's JNI-created C++ counterpart is equivalent to the expected C++
+ * BrushBehavior.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun matchesNativePredefinedBehavior(
+ nativePointerToActualBrushBehavior: Long
+ ): Boolean
+
+ /**
+ * Creates an expected C++ CubicBezier BrushBehavior and returns true if every property of the
+ * Kotlin BrushBehavior's JNI-created C++ counterpart is equivalent to the expected C++
+ * BrushBehavior.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun matchesNativeCubicBezierBehavior(
+ nativePointerToActualBrushBehavior: Long
+ ): Boolean
+
+ /**
+ * Creates an expected C++ Linear BrushBehavior and returns true if every property of the Kotlin
+ * BrushBehavior's JNI-created C++ counterpart is equivalent to the expected C++ BrushBehavior.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun matchesNativeLinearBehavior(
+ nativePointerToActualBrushBehavior: Long
+ ): Boolean
+}
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushCoatTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushCoatTest.kt
new file mode 100644
index 0000000..a3504a3
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushCoatTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalInkCustomBrushApi::class)
+@RunWith(JUnit4::class)
+class BrushCoatTest {
+ @Test
+ fun constructor_withValidArguments_returnsABrushCoat() {
+ assertThat(BrushCoat(customTip, customPaint)).isNotNull()
+ }
+
+ @Test
+ fun constructor_withDefaultArguments_returnsABrushCoat() {
+ assertThat(BrushCoat(BrushTip(), BrushPaint())).isNotNull()
+ }
+
+ @Test
+ fun hashCode_withIdenticalValues_matches() {
+ assertThat(newCustomBrushCoat().hashCode()).isEqualTo(newCustomBrushCoat().hashCode())
+ }
+
+ @Test
+ fun equals_comparesValues() {
+ val brushCoat = BrushCoat(customTip, customPaint)
+ val differentTip = BrushTip()
+ val differentPaint = BrushPaint()
+
+ // same values are equal.
+ assertThat(brushCoat).isEqualTo(BrushCoat(customTip, customPaint))
+
+ // different values are not equal.
+ assertThat(brushCoat).isNotEqualTo(null)
+ assertThat(brushCoat).isNotEqualTo(Any())
+ assertThat(brushCoat).isNotEqualTo(brushCoat.copy(tip = differentTip))
+ assertThat(brushCoat).isNotEqualTo(brushCoat.copy(paint = differentPaint))
+ }
+
+ @Test
+ fun toString_returnsExpectedValues() {
+ assertThat(BrushCoat().toString())
+ .isEqualTo(
+ "BrushCoat(tips=[BrushTip(scale=(1.0, 1.0), " +
+ "cornerRounding=1.0, slant=0.0, pinch=0.0, rotation=0.0, opacityMultiplier=1.0, " +
+ "particleGapDistanceScale=0.0, particleGapDurationMillis=0, behaviors=[])], " +
+ "paint=BrushPaint(textureLayers=[]))"
+ )
+ }
+
+ @Test
+ fun copy_whenSameContents_returnsSameInstance() {
+ val customCoat = BrushCoat(customTip, customPaint)
+
+ // A pure copy returns `this`.
+ val copy = customCoat.copy()
+ assertThat(copy).isSameInstanceAs(customCoat)
+ }
+
+ @Test
+ fun copy_withArguments_createsCopyWithChanges() {
+ val brushCoat = BrushCoat(customTip, customPaint)
+ val differentTip = BrushTip()
+ val differentPaint = BrushPaint()
+
+ assertThat(brushCoat.copy(tip = differentTip))
+ .isEqualTo(BrushCoat(differentTip, customPaint))
+ assertThat(brushCoat.copy(paint = differentPaint))
+ .isEqualTo(BrushCoat(customTip, differentPaint))
+ }
+
+ @Test
+ fun builder_createsExpectedBrushCoat() {
+ val coat = BrushCoat.Builder().setTip(customTip).setPaint(customPaint).build()
+ assertThat(coat).isEqualTo(BrushCoat(customTip, customPaint))
+ }
+
+ /**
+ * Creates an expected C++ BrushCoat with defaults and returns true if every property of the
+ * Kotlin BrushCoat's JNI-created C++ counterpart is equivalent to the expected C++ BrushCoat.
+ */
+ private external fun matchesDefaultCoat(
+ brushCoatNativePointer: Long
+ ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ /**
+ * Creates an expected C++ BrushCoat with custom values and returns true if every property of
+ * the Kotlin BrushCoat's JNI-created C++ counterpart is equivalent to the expected C++
+ * BrushCoat.
+ */
+ private external fun matchesMultiBehaviorTipCoat(
+ brushCoatNativePointer: Long
+ ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ /** Brush behavior with every field different from default values. */
+ private val customBehavior =
+ BrushBehavior(
+ source = BrushBehavior.Source.TILT_IN_RADIANS,
+ target = BrushBehavior.Target.HEIGHT_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = 1.1f,
+ targetModifierRangeUpperBound = 1.7f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.MIRROR,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ isFallbackFor = BrushBehavior.OptionalInputProperty.TILT_X_AND_Y,
+ )
+
+ /** Brush tip with every field different from default values and non-empty behaviors. */
+ private val customTip =
+ BrushTip(
+ scaleX = 0.1f,
+ scaleY = 0.2f,
+ cornerRounding = 0.3f,
+ slant = 0.4f,
+ pinch = 0.5f,
+ rotation = 0.6f,
+ opacityMultiplier = 0.7f,
+ particleGapDistanceScale = 0.8f,
+ particleGapDurationMillis = 9L,
+ listOf<BrushBehavior>(customBehavior),
+ )
+
+ /**
+ * Brush Paint with every field different from default values, including non-empty texture
+ * layers.
+ */
+ private val customPaint =
+ BrushPaint(
+ listOf(
+ BrushPaint.TextureLayer(
+ colorTextureUri = "ink://ink/texture:test-one",
+ sizeX = 123.45F,
+ sizeY = 678.90F,
+ offsetX = 0.123f,
+ offsetY = 0.678f,
+ rotation = 0.1f,
+ opacity = 0.123f,
+ BrushPaint.TextureSizeUnit.STROKE_COORDINATES,
+ BrushPaint.TextureOrigin.STROKE_SPACE_ORIGIN,
+ BrushPaint.TextureMapping.TILING,
+ ),
+ BrushPaint.TextureLayer(
+ colorTextureUri = "ink://ink/texture:test-two",
+ sizeX = 256F,
+ sizeY = 256F,
+ offsetX = 0.456f,
+ offsetY = 0.567f,
+ rotation = 0.2f,
+ opacity = 0.987f,
+ BrushPaint.TextureSizeUnit.STROKE_COORDINATES,
+ BrushPaint.TextureOrigin.STROKE_SPACE_ORIGIN,
+ BrushPaint.TextureMapping.TILING,
+ ),
+ )
+ )
+
+ /** Brush Coat with every field different from default values. */
+ private fun newCustomBrushCoat(): BrushCoat = BrushCoat(customTip, customPaint)
+}
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushFamilyTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushFamilyTest.kt
new file mode 100644
index 0000000..a9197d5
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushFamilyTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalInkCustomBrushApi::class)
+@RunWith(JUnit4::class)
+class BrushFamilyTest {
+ @Test
+ fun constructor_withValidArguments_returnsABrushFamily() {
+ assertThat(BrushFamily(customTip, customPaint, customUri)).isNotNull()
+ }
+
+ @Test
+ fun constructor_withDefaultArguments_returnsABrushFamily() {
+ assertThat(BrushFamily(BrushTip(), BrushPaint(), uri = null)).isNotNull()
+ assertThat(BrushFamily(BrushTip(), BrushPaint(), uri = "")).isNotNull()
+ }
+
+ @Test
+ fun constructor_withBadUri_throws() {
+ assertFailsWith<IllegalArgumentException> { BrushFamily(customTip, customPaint, "baduri") }
+ }
+
+ @Test
+ fun hashCode_withIdenticalValues_matches() {
+ assertThat(newCustomBrushFamily().hashCode()).isEqualTo(newCustomBrushFamily().hashCode())
+ }
+
+ @Test
+ fun equals_comparesValues() {
+ val brushFamily = BrushFamily(customTip, customPaint, customUri)
+ val differentCoat = BrushCoat(BrushTip(), BrushPaint())
+ val differentUri = null
+
+ // same values are equal.
+ assertThat(brushFamily).isEqualTo(BrushFamily(customTip, customPaint, customUri))
+
+ // different values are not equal.
+ assertThat(brushFamily).isNotEqualTo(null)
+ assertThat(brushFamily).isNotEqualTo(Any())
+ assertThat(brushFamily).isNotEqualTo(brushFamily.copy(coat = differentCoat))
+ assertThat(brushFamily).isNotEqualTo(brushFamily.copy(uri = differentUri))
+ }
+
+ @Test
+ fun toString_returnsExpectedValues() {
+ assertThat(BrushFamily().toString())
+ .isEqualTo(
+ "BrushFamily(coats=[BrushCoat(tips=[BrushTip(scale=(1.0, 1.0), " +
+ "cornerRounding=1.0, slant=0.0, pinch=0.0, rotation=0.0, opacityMultiplier=1.0, " +
+ "particleGapDistanceScale=0.0, particleGapDurationMillis=0, " +
+ "behaviors=[])], paint=BrushPaint(textureLayers=[]))], uri=null)"
+ )
+ }
+
+ @Test
+ fun copy_whenSameContents_returnsSameInstance() {
+ val customFamily = BrushFamily(customTip, customPaint, customUri)
+
+ // A pure copy returns `this`.
+ val copy = customFamily.copy()
+ assertThat(copy).isSameInstanceAs(customFamily)
+ }
+
+ @Test
+ fun copy_withArguments_createsCopyWithChanges() {
+ val brushFamily = BrushFamily(customTip, customPaint, customUri)
+ val differentCoats = listOf(BrushCoat(BrushTip(), BrushPaint()))
+ val differentUri = null
+
+ assertThat(brushFamily.copy(coats = differentCoats))
+ .isEqualTo(BrushFamily(differentCoats, customUri))
+ assertThat(brushFamily.copy(uri = differentUri))
+ .isEqualTo(BrushFamily(customTip, customPaint, differentUri))
+ }
+
+ @Test
+ fun builder_createsExpectedBrushFamily() {
+ val family = BrushFamily.Builder().setCoat(customTip, customPaint).setUri(customUri).build()
+ assertThat(family).isEqualTo(BrushFamily(customTip, customPaint, customUri))
+ }
+
+ /**
+ * Creates an expected C++ BrushFamily with defaults and returns true if every property of the
+ * Kotlin BrushFamily's JNI-created C++ counterpart is equivalent to the expected C++
+ * BrushFamily.
+ */
+ private external fun matchesDefaultFamily(
+ brushFamilyNativePointer: Long
+ ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ /**
+ * Creates an expected C++ BrushFamily with custom values and returns true if every property of
+ * the Kotlin BrushFamily's JNI-created C++ counterpart is equivalent to the expected C++
+ * BrushFamily.
+ */
+ private external fun matchesMultiBehaviorTipFamily(
+ brushFamilyNativePointer: Long
+ ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ private val customUri = "/brush-family:inkpen:1"
+
+ /** Brush behavior with every field different from default values. */
+ private val customBehavior =
+ BrushBehavior(
+ source = BrushBehavior.Source.TILT_IN_RADIANS,
+ target = BrushBehavior.Target.HEIGHT_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = 1.1f,
+ targetModifierRangeUpperBound = 1.7f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.MIRROR,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ isFallbackFor = BrushBehavior.OptionalInputProperty.TILT_X_AND_Y,
+ )
+
+ /** Brush tip with every field different from default values and non-empty behaviors. */
+ private val customTip =
+ BrushTip(
+ scaleX = 0.1f,
+ scaleY = 0.2f,
+ cornerRounding = 0.3f,
+ slant = 0.4f,
+ pinch = 0.5f,
+ rotation = 0.6f,
+ opacityMultiplier = 0.7f,
+ particleGapDistanceScale = 0.8f,
+ particleGapDurationMillis = 9L,
+ listOf(customBehavior),
+ )
+
+ /**
+ * Brush Paint with every field different from default values, including non-empty texture
+ * layers.
+ */
+ private val customPaint =
+ BrushPaint(
+ listOf(
+ BrushPaint.TextureLayer(
+ colorTextureUri = "ink://ink/texture:test-one",
+ sizeX = 123.45F,
+ sizeY = 678.90F,
+ offsetX = 0.123f,
+ offsetY = 0.678f,
+ rotation = 0.1f,
+ opacity = 0.123f,
+ BrushPaint.TextureSizeUnit.STROKE_COORDINATES,
+ BrushPaint.TextureOrigin.STROKE_SPACE_ORIGIN,
+ BrushPaint.TextureMapping.TILING,
+ ),
+ BrushPaint.TextureLayer(
+ colorTextureUri = "ink://ink/texture:test-two",
+ sizeX = 256F,
+ sizeY = 256F,
+ offsetX = 0.456f,
+ offsetY = 0.567f,
+ rotation = 0.2f,
+ opacity = 0.987f,
+ BrushPaint.TextureSizeUnit.STROKE_COORDINATES,
+ BrushPaint.TextureOrigin.STROKE_SPACE_ORIGIN,
+ BrushPaint.TextureMapping.TILING,
+ ),
+ )
+ )
+
+ /** Brush Family with every field different from default values. */
+ private fun newCustomBrushFamily(): BrushFamily = BrushFamily(customTip, customPaint, customUri)
+}
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushPaintTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushPaintTest.kt
new file mode 100644
index 0000000..1982d5f
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushPaintTest.kt
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.ink.geometry.Angle
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalInkCustomBrushApi::class)
+@RunWith(JUnit4::class)
+class BrushPaintTest {
+
+ // region BrushPaint class tests
+ @Test
+ fun constructor_withValidArguments_returnsABrushPaint() {
+ assertThat(
+ BrushPaint(
+ listOf(
+ BrushPaint.TextureLayer(
+ colorTextureUri = makeTestTextureUri(1),
+ sizeX = 123.45F,
+ sizeY = 678.90F,
+ offsetX = 0.1f,
+ offsetY = 0.2f,
+ rotation = Angle.QUARTER_TURN_RADIANS,
+ opacity = 0.3f,
+ BrushPaint.TextureSizeUnit.STROKE_COORDINATES,
+ BrushPaint.TextureOrigin.STROKE_SPACE_ORIGIN,
+ BrushPaint.TextureMapping.TILING,
+ ),
+ BrushPaint.TextureLayer(
+ colorTextureUri = makeTestTextureUri(2),
+ sizeX = 256F,
+ sizeY = 256F,
+ offsetX = 0.8f,
+ offsetY = 0.9f,
+ rotation = Angle.HALF_TURN_RADIANS,
+ opacity = 0.7f,
+ BrushPaint.TextureSizeUnit.STROKE_COORDINATES,
+ BrushPaint.TextureOrigin.FIRST_STROKE_INPUT,
+ BrushPaint.TextureMapping.TILING,
+ ),
+ )
+ )
+ )
+ .isNotNull()
+ }
+
+ @Test
+ fun constructor_withDefaultArguments_returnsABrushPaint() {
+ assertThat(BrushPaint()).isNotNull()
+ }
+
+ @Test
+ fun hashCode_withIdenticalValues_matches() {
+ assertThat(BrushPaint(listOf(makeTestTextureLayer())).hashCode())
+ .isEqualTo(BrushPaint(listOf(makeTestTextureLayer())).hashCode())
+ }
+
+ @Test
+ fun equals_comparesValues() {
+ val customPaint = makeTestPaint()
+ val defaultPaint = BrushPaint()
+ // same values are equal.
+ assertThat(customPaint).isEqualTo(makeTestPaint())
+
+ // different values are not equal.
+ assertThat(customPaint).isNotEqualTo(null)
+ assertThat(customPaint).isNotEqualTo(Any())
+ assertThat(customPaint).isNotEqualTo(defaultPaint)
+ }
+
+ @Test
+ fun toString_returnsExpectedValues() {
+ val string = makeTestPaint().toString()
+ assertThat(string).contains("BrushPaint")
+ assertThat(string).contains("textureLayers")
+ }
+
+ // endregion
+
+ // region TextureLayer class tests
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun textureLayerConstructor_withInvalidSizes_throwsIllegalArgumentException() {
+ val fakeValidUri = makeTestTextureUri()
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, -32F, 64F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, 32F, -64F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, -32F, -64F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, 0F, 128F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, 128F, 0F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, Float.NaN, 128F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, 128F, Float.NaN)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, Float.POSITIVE_INFINITY, 128F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, 128F, Float.POSITIVE_INFINITY)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, Float.NEGATIVE_INFINITY, 128F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, 128F, Float.NEGATIVE_INFINITY)
+ }
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun textureLayerConstructor_withInvalidOffsetX_throwsIllegalArgumentException() {
+ val fakeValidUri = makeTestTextureUri()
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, sizeX = 1f, sizeY = 1f, offsetX = Float.NaN)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, sizeX = 1f, sizeY = 1f, offsetX = -0.001f)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, sizeX = 1f, sizeY = 1f, offsetX = 1.001f)
+ }
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun textureLayerConstructor_withInvalidOffsetY_throwsIllegalArgumentException() {
+ val fakeValidUri = makeTestTextureUri()
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, sizeX = 1f, sizeY = 1f, offsetY = Float.NaN)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, sizeX = 1f, sizeY = 1f, offsetY = -0.001f)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, sizeX = 1f, sizeY = 1f, offsetY = 1.001f)
+ }
+ }
+
+ @Test
+ fun textureLayerConstructor_withInvalidRotation_throwsIllegalArgumentException() {
+ val fakeValidUri = makeTestTextureUri()
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, sizeX = 1f, sizeY = 1f, rotation = Float.NaN)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(
+ fakeValidUri,
+ sizeX = 1f,
+ sizeY = 1f,
+ rotation = Float.POSITIVE_INFINITY,
+ )
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(
+ fakeValidUri,
+ sizeX = 1f,
+ sizeY = 1f,
+ rotation = Float.NEGATIVE_INFINITY,
+ )
+ }
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun textureLayerConstructor_withInvalidOpacity_throwsIllegalArgumentException() {
+ val fakeValidUri = makeTestTextureUri()
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, sizeX = 1f, sizeY = 1f, opacity = Float.NaN)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, sizeX = 1f, sizeY = 1f, opacity = -0.001f)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ BrushPaint.TextureLayer(fakeValidUri, sizeX = 1f, sizeY = 1f, opacity = 1.001f)
+ }
+ }
+
+ @Test
+ fun textureLayerHashCode_withIdenticalValues_matches() {
+ assertThat(makeTestTextureLayer().hashCode()).isEqualTo(makeTestTextureLayer().hashCode())
+ }
+
+ @Test
+ fun textureLayerEquals_checksEqualityOfValues() {
+ val layer =
+ BrushPaint.TextureLayer(
+ colorTextureUri = makeTestTextureUri(),
+ sizeX = 128F,
+ sizeY = 128F,
+ offsetX = 0.1f,
+ offsetY = 0.2f,
+ rotation = Angle.QUARTER_TURN_RADIANS,
+ opacity = 0.3f,
+ BrushPaint.TextureSizeUnit.BRUSH_SIZE,
+ BrushPaint.TextureOrigin.LAST_STROKE_INPUT,
+ BrushPaint.TextureMapping.WINDING,
+ BrushPaint.BlendMode.SRC_IN,
+ )
+
+ // same values.
+ assertThat(layer)
+ .isEqualTo(
+ BrushPaint.TextureLayer(
+ colorTextureUri = makeTestTextureUri(),
+ sizeX = 128F,
+ sizeY = 128F,
+ offsetX = 0.1f,
+ offsetY = 0.2f,
+ rotation = Angle.QUARTER_TURN_RADIANS,
+ opacity = 0.3f,
+ BrushPaint.TextureSizeUnit.BRUSH_SIZE,
+ BrushPaint.TextureOrigin.LAST_STROKE_INPUT,
+ BrushPaint.TextureMapping.WINDING,
+ BrushPaint.BlendMode.SRC_IN,
+ )
+ )
+
+ // different values.
+ assertThat(layer).isNotEqualTo(null)
+ assertThat(layer).isNotEqualTo(Any())
+ assertThat(layer).isNotEqualTo(layer.copy(colorTextureUri = makeTestTextureUri(2)))
+ assertThat(layer).isNotEqualTo(layer.copy(sizeX = 999F))
+ assertThat(layer).isNotEqualTo(layer.copy(sizeY = 999F))
+ assertThat(layer).isNotEqualTo(layer.copy(offsetX = 0.999F))
+ assertThat(layer).isNotEqualTo(layer.copy(offsetY = 0.999F))
+ assertThat(layer).isNotEqualTo(layer.copy(rotation = Angle.HALF_TURN_RADIANS))
+ assertThat(layer).isNotEqualTo(layer.copy(opacity = 0.999f))
+ assertThat(layer)
+ .isNotEqualTo(layer.copy(sizeUnit = BrushPaint.TextureSizeUnit.STROKE_COORDINATES))
+ assertThat(layer)
+ .isNotEqualTo(layer.copy(origin = BrushPaint.TextureOrigin.FIRST_STROKE_INPUT))
+ assertThat(layer).isNotEqualTo(layer.copy(mapping = BrushPaint.TextureMapping.TILING))
+ assertThat(layer).isNotEqualTo(layer.copy(blendMode = BrushPaint.BlendMode.MODULATE))
+ }
+
+ @Test
+ fun textureLayerCopy_createsCopy() {
+ val layer = makeTestTextureLayer()
+ val copy = layer.copy()
+
+ // Pure copy returns `this`.
+ assertThat(copy).isSameInstanceAs(layer)
+ }
+
+ @Test
+ fun textureLayerCopy_withArguments_createsCopyWithChanges() {
+ val originalLayer =
+ BrushPaint.TextureLayer(
+ colorTextureUri = makeTestTextureUri(),
+ sizeX = 128F,
+ sizeY = 128F,
+ offsetX = 0.1f,
+ offsetY = 0.2f,
+ rotation = Angle.QUARTER_TURN_RADIANS,
+ opacity = 0.3f,
+ BrushPaint.TextureSizeUnit.BRUSH_SIZE,
+ BrushPaint.TextureOrigin.FIRST_STROKE_INPUT,
+ BrushPaint.TextureMapping.WINDING,
+ BrushPaint.BlendMode.SRC_IN,
+ )
+ val changedSizeX = originalLayer.copy(sizeX = 999F)
+
+ // sizeX changed.
+ assertThat(changedSizeX).isNotEqualTo(originalLayer)
+ assertThat(changedSizeX.sizeX).isNotEqualTo(originalLayer.sizeX)
+
+ assertThat(changedSizeX)
+ .isEqualTo(
+ BrushPaint.TextureLayer(
+ colorTextureUri = makeTestTextureUri(),
+ sizeX = 999F, // Changed
+ sizeY = 128F,
+ offsetX = 0.1f,
+ offsetY = 0.2f,
+ rotation = Angle.QUARTER_TURN_RADIANS,
+ opacity = 0.3f,
+ BrushPaint.TextureSizeUnit.BRUSH_SIZE,
+ BrushPaint.TextureOrigin.FIRST_STROKE_INPUT,
+ BrushPaint.TextureMapping.WINDING,
+ BrushPaint.BlendMode.SRC_IN,
+ )
+ )
+ }
+
+ @Test
+ fun textureLayerToString_returnsExpectedValues() {
+ val string = makeTestTextureLayer().toString()
+ assertThat(string).contains("TextureLayer")
+ assertThat(string).contains("colorTextureUri")
+ assertThat(string).contains("size")
+ assertThat(string).contains("offset")
+ assertThat(string).contains("rotation")
+ assertThat(string).contains("opacity")
+ assertThat(string).contains("sizeUnit")
+ assertThat(string).contains("origin")
+ assertThat(string).contains("mapping")
+ assertThat(string).contains("blendMode")
+ }
+
+ // endregion
+
+ // region SizeUnit class tests
+ @Test
+ fun sizeUnitConstants_areDistinct() {
+ val set =
+ setOf(
+ BrushPaint.TextureSizeUnit.BRUSH_SIZE,
+ BrushPaint.TextureSizeUnit.STROKE_SIZE,
+ BrushPaint.TextureSizeUnit.STROKE_COORDINATES,
+ )
+ assertThat(set).hasSize(3)
+ }
+
+ @Test
+ fun sizeUnitHashCode_withIdenticalValues_match() {
+ assertThat(BrushPaint.TextureSizeUnit.STROKE_COORDINATES.hashCode())
+ .isEqualTo(BrushPaint.TextureSizeUnit.STROKE_COORDINATES.hashCode())
+ }
+
+ @Test
+ fun sizeUnitEquals_checksEqualityOfValues() {
+ assertThat(BrushPaint.TextureSizeUnit.STROKE_COORDINATES)
+ .isEqualTo(BrushPaint.TextureSizeUnit.STROKE_COORDINATES)
+ assertThat(BrushPaint.TextureSizeUnit.STROKE_COORDINATES)
+ .isNotEqualTo(BrushPaint.TextureSizeUnit.BRUSH_SIZE)
+ }
+
+ @Test
+ fun sizeUnitToString_returnsCorrectString() {
+ assertThat(BrushPaint.TextureSizeUnit.BRUSH_SIZE.toString())
+ .isEqualTo("BrushPaint.TextureSizeUnit.BRUSH_SIZE")
+ assertThat(BrushPaint.TextureSizeUnit.STROKE_SIZE.toString())
+ .isEqualTo("BrushPaint.TextureSizeUnit.STROKE_SIZE")
+ assertThat(BrushPaint.TextureSizeUnit.STROKE_COORDINATES.toString())
+ .isEqualTo("BrushPaint.TextureSizeUnit.STROKE_COORDINATES")
+ }
+
+ // endregion
+
+ // region Origin class tests
+ @Test
+ fun originConstants_areDistint() {
+ val set =
+ setOf(
+ BrushPaint.TextureOrigin.STROKE_SPACE_ORIGIN,
+ BrushPaint.TextureOrigin.FIRST_STROKE_INPUT,
+ BrushPaint.TextureOrigin.LAST_STROKE_INPUT,
+ )
+ assertThat(set).hasSize(3)
+ }
+
+ @Test
+ fun originHashCode_withIdenticalValues_match() {
+ assertThat(BrushPaint.TextureOrigin.FIRST_STROKE_INPUT.hashCode())
+ .isEqualTo(BrushPaint.TextureOrigin.FIRST_STROKE_INPUT.hashCode())
+ }
+
+ @Test
+ fun originEquals_checksEqualityOfValues() {
+ assertThat(BrushPaint.TextureOrigin.FIRST_STROKE_INPUT)
+ .isEqualTo(BrushPaint.TextureOrigin.FIRST_STROKE_INPUT)
+ assertThat(BrushPaint.TextureOrigin.FIRST_STROKE_INPUT)
+ .isNotEqualTo(BrushPaint.TextureOrigin.LAST_STROKE_INPUT)
+ }
+
+ @Test
+ fun originToString_returnsCorrectString() {
+ assertThat(BrushPaint.TextureOrigin.STROKE_SPACE_ORIGIN.toString())
+ .isEqualTo("BrushPaint.TextureOrigin.STROKE_SPACE_ORIGIN")
+ assertThat(BrushPaint.TextureOrigin.FIRST_STROKE_INPUT.toString())
+ .isEqualTo("BrushPaint.TextureOrigin.FIRST_STROKE_INPUT")
+ assertThat(BrushPaint.TextureOrigin.LAST_STROKE_INPUT.toString())
+ .isEqualTo("BrushPaint.TextureOrigin.LAST_STROKE_INPUT")
+ }
+
+ // endregion
+
+ // region Mapping class tests
+ @Test
+ fun mappingConstants_areDistint() {
+ val set = setOf(BrushPaint.TextureMapping.TILING, BrushPaint.TextureMapping.WINDING)
+ assertThat(set).hasSize(2)
+ }
+
+ @Test
+ fun mappingHashCode_withIdenticalValues_match() {
+ assertThat(BrushPaint.TextureMapping.TILING.hashCode())
+ .isEqualTo(BrushPaint.TextureMapping.TILING.hashCode())
+ }
+
+ @Test
+ fun mappingEquals_checksEqualityOfValues() {
+ assertThat(BrushPaint.TextureMapping.TILING).isEqualTo(BrushPaint.TextureMapping.TILING)
+ assertThat(BrushPaint.TextureMapping.TILING).isNotEqualTo(BrushPaint.TextureMapping.WINDING)
+ }
+
+ @Test
+ fun mappingToString_returnsCorrectString() {
+ assertThat(BrushPaint.TextureMapping.TILING.toString())
+ .isEqualTo("BrushPaint.TextureMapping.TILING")
+ assertThat(BrushPaint.TextureMapping.WINDING.toString())
+ .isEqualTo("BrushPaint.TextureMapping.WINDING")
+ }
+
+ // endregion
+
+ // region BlendMode class tests
+ @Test
+ fun textureBlendModeConstants_areDistinct() {
+ val set =
+ setOf(
+ BrushPaint.BlendMode.MODULATE,
+ BrushPaint.BlendMode.DST_IN,
+ BrushPaint.BlendMode.DST_OUT,
+ BrushPaint.BlendMode.SRC_ATOP,
+ BrushPaint.BlendMode.SRC_IN,
+ BrushPaint.BlendMode.SRC_OVER,
+ )
+ assertThat(set).hasSize(6)
+ }
+
+ @Test
+ fun textureBlendModeHashCode_withIdenticalValues_match() {
+ assertThat(BrushPaint.BlendMode.MODULATE.hashCode())
+ .isEqualTo(BrushPaint.BlendMode.MODULATE.hashCode())
+ }
+
+ @Test
+ fun textureBlendModeEquals_checksEqualityOfValues() {
+ assertThat(BrushPaint.BlendMode.MODULATE).isEqualTo(BrushPaint.BlendMode.MODULATE)
+ assertThat(BrushPaint.BlendMode.MODULATE).isNotEqualTo(BrushPaint.BlendMode.SRC_OVER)
+ }
+
+ @Test
+ fun textureBlendModeToString_returnsCorrectString() {
+ assertThat(BrushPaint.BlendMode.MODULATE.toString()).contains("MODULATE")
+ assertThat(BrushPaint.BlendMode.DST_IN.toString()).contains("DST_IN")
+ assertThat(BrushPaint.BlendMode.DST_OUT.toString()).contains("DST_OUT")
+ assertThat(BrushPaint.BlendMode.SRC_ATOP.toString()).contains("SRC_ATOP")
+ assertThat(BrushPaint.BlendMode.SRC_IN.toString()).contains("SRC_IN")
+ assertThat(BrushPaint.BlendMode.SRC_OVER.toString()).contains("SRC_OVER")
+ }
+
+ // endregion
+
+ private external fun matchesNativeCustomPaint(
+ brushPaintNativePointer: Long
+ ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ private fun makeTestTextureUri(version: Int = 0) =
+ "ink://ink/texture:test-texture" + if (version == 0) "" else ":" + version
+
+ private fun makeTestTextureLayer() =
+ BrushPaint.TextureLayer(
+ colorTextureUri = makeTestTextureUri(),
+ sizeX = 128F,
+ sizeY = 128F,
+ offsetX = 0.1f,
+ offsetY = 0.2f,
+ rotation = Angle.QUARTER_TURN_RADIANS,
+ opacity = 0.3f,
+ BrushPaint.TextureSizeUnit.BRUSH_SIZE,
+ BrushPaint.TextureOrigin.FIRST_STROKE_INPUT,
+ BrushPaint.TextureMapping.WINDING,
+ BrushPaint.BlendMode.SRC_IN,
+ )
+
+ private fun makeTestPaint() = BrushPaint(listOf(makeTestTextureLayer()))
+}
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTest.kt
index 4e1045f..ca8c4d4 100644
--- a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTest.kt
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,20 +17,392 @@
package androidx.ink.brush
import androidx.ink.brush.color.Color
-import kotlin.test.Test
-import kotlin.test.assertEquals
+import androidx.ink.brush.color.colorspace.ColorSpaces
+import androidx.ink.brush.color.toArgb
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+@OptIn(ExperimentalInkCustomBrushApi::class)
+@RunWith(JUnit4::class)
class BrushTest {
+ private val size = 10F
+ private val epsilon = 1F
+ private val color = Color(red = 230, green = 115, blue = 140, alpha = 255)
+ private val family = BrushFamily(uri = "/brush-family:inkpen:1")
+
@Test
- fun testSetAndGetColor() {
- val originalColor = Color.Cyan.value.toLong()
- val brush = Brush(color = originalColor, size = 2.5f)
- assertEquals(brush.color, originalColor)
+ fun constructor_withValidArguments_returnsABrush() {
+ val brush = Brush.createWithColorLong(family, color.value.toLong(), size, epsilon)
+ assertThat(brush).isNotNull()
+ assertThat(brush.family).isEqualTo(family)
+ assertThat(brush.colorLong).isEqualTo(color.value.toLong())
+ assertThat(brush.colorIntArgb).isEqualTo(color.toArgb())
+ assertThat(brush.colorLong).isEqualTo(color.value.toLong())
+ assertThat(brush.size).isEqualTo(size)
+ assertThat(brush.epsilon).isEqualTo(epsilon)
}
@Test
- fun testSetAndGetSize() {
- val brush = Brush(color = Color.DarkGray.value.toLong(), size = 2.5f)
- assertEquals(brush.size, 2.5f)
+ @Suppress("Range") // Testing error cases.
+ fun constructor_withBadSize_willThrow() {
+ assertFailsWith<IllegalArgumentException> {
+ Brush(family, color, -2F, epsilon) // non-positive size.
+ }
+
+ assertFailsWith<IllegalArgumentException> {
+ Brush(family, color, Float.POSITIVE_INFINITY, epsilon) // non-finite size.
+ }
+
+ assertFailsWith<IllegalArgumentException> {
+ Brush(family, color, Float.NaN, epsilon) // non-finite size.
+ }
}
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun constructor_withBadEpsilon_willThrow() {
+ assertFailsWith<IllegalArgumentException> {
+ Brush(family, color, size, -2F) // non-positive epsilon.
+ }
+
+ assertFailsWith<IllegalArgumentException> {
+ Brush(family, color, size, Float.POSITIVE_INFINITY) // non-finite epsilon.
+ }
+
+ assertFailsWith<IllegalArgumentException> {
+ Brush(family, color, size, Float.NaN) // non-finite epsilon.
+ }
+ }
+
+ @Test
+ fun colorAccessors_areAllEquivalent() {
+ val color = Color(red = 230, green = 115, blue = 140, alpha = 196)
+ val brush = Brush.createWithColorLong(family, color.value.toLong(), size, epsilon)
+
+ assertThat(brush.colorIntArgb).isEqualTo(color.toArgb())
+ assertThat(brush.colorLong).isEqualTo(color.value.toLong())
+ }
+
+ @Test
+ fun withColorIntArgb_withLowAlpha_returnsBrushWithCorrectColor() {
+ val brush = Brush.createWithColorIntArgb(family, 0x12345678, size, epsilon)
+ assertThat(brush.colorIntArgb).isEqualTo(0x12345678)
+ }
+
+ @Test
+ fun withColorIntArgb_withHighAlpha_returnsBrushWithCorrectColor() {
+ val brush = Brush.createWithColorIntArgb(family, 0xAA123456.toInt(), size, epsilon)
+ assertThat(brush.colorIntArgb).isEqualTo(0xAA123456.toInt())
+ }
+
+ @Test
+ fun withColorLong_returnsBrushWithCorrectColor() {
+ val colorLong = Color(0.9f, 0.45f, 0.55f, 0.15f, ColorSpaces.DisplayP3).value.toLong()
+ val brush = Brush.createWithColorLong(family, colorLong, size, epsilon)
+ assertThat(brush.colorLong).isEqualTo(colorLong)
+ }
+
+ @Test
+ fun withColorLong_inUnsupportedColorSpace_returnsBrushWithConvertedColor() {
+ val colorLong = Color(0.9f, 0.45f, 0.55f, 0.15f, ColorSpaces.AdobeRgb).value.toLong()
+ val brush = Brush.createWithColorLong(family, colorLong, size, epsilon)
+
+ val expectedColor = Color(colorLong.toULong()).convert(ColorSpaces.DisplayP3)
+ assertThat(brush.colorLong).isEqualTo(expectedColor.value.toLong())
+ assertThat(brush.colorIntArgb).isEqualTo(expectedColor.toArgb())
+ }
+
+ @Test
+ fun equals_returnsTrueForIdenticalBrushes() {
+ val brush = Brush(family, color, size, epsilon)
+ val otherBrush = Brush(family, color, size, epsilon)
+ assertThat(brush == brush).isTrue()
+ assertThat(brush == otherBrush).isTrue()
+ assertThat(otherBrush == brush).isTrue()
+ }
+
+ @Test
+ fun hashCode_isEqualForIdenticalBrushes() {
+ val brush = Brush(family, color, size, epsilon)
+ val otherBrush = Brush(family, color, size, epsilon)
+ assertThat(brush == brush).isTrue()
+ assertThat(brush == otherBrush).isTrue()
+ assertThat(otherBrush == brush).isTrue()
+ }
+
+ @Test
+ fun equals_returnsFalseIfAnyFieldsDiffer() {
+ val brush = Brush(family, color, size, epsilon)
+
+ val differentFamilyBrush =
+ Brush(BrushFamily(uri = "/brush-family:pencil:1"), color, size, epsilon)
+ assertThat(brush == differentFamilyBrush).isFalse()
+ assertThat(differentFamilyBrush == brush).isFalse()
+ assertThat(brush != differentFamilyBrush).isTrue()
+ assertThat(differentFamilyBrush != brush).isTrue()
+
+ val otherColor =
+ Color(red = 1F, green = 0F, blue = 0F, alpha = 1F, colorSpace = ColorSpaces.DisplayP3)
+ .value
+ .toLong()
+ val differentcolorBrush = Brush.createWithColorLong(family, otherColor, size, epsilon)
+ assertThat(brush == differentcolorBrush).isFalse()
+ assertThat(differentcolorBrush == brush).isFalse()
+ assertThat(brush != differentcolorBrush).isTrue()
+ assertThat(differentcolorBrush != brush).isTrue()
+
+ val differentSizeBrush = Brush(family, color, 9.0f, epsilon)
+ assertThat(brush == differentSizeBrush).isFalse()
+ assertThat(differentSizeBrush == brush).isFalse()
+ assertThat(brush != differentSizeBrush).isTrue()
+ assertThat(differentSizeBrush != brush).isTrue()
+
+ val differentEpsilonBrush = Brush(family, color, size, 1.1f)
+ assertThat(brush == differentEpsilonBrush).isFalse()
+ assertThat(differentEpsilonBrush == brush).isFalse()
+ assertThat(brush != differentEpsilonBrush).isTrue()
+ assertThat(differentEpsilonBrush != brush).isTrue()
+ }
+
+ @Test
+ fun hashCode_differsIfAnyFieldsDiffer() {
+ val brush = Brush(family, color, size, epsilon)
+
+ val differentFamilyBrush =
+ Brush(BrushFamily(uri = "/brush-family:pencil:1"), color, size, epsilon)
+ assertThat(differentFamilyBrush.hashCode()).isNotEqualTo(brush.hashCode())
+
+ val otherColor =
+ Color(red = 1F, green = 0F, blue = 0F, alpha = 1F, colorSpace = ColorSpaces.DisplayP3)
+ .value
+ .toLong()
+ val differentcolorBrush = Brush.createWithColorLong(family, otherColor, size, epsilon)
+ assertThat(differentcolorBrush.hashCode()).isNotEqualTo(brush.hashCode())
+
+ val differentSizeBrush = Brush(family, color, 9.0f, epsilon)
+ assertThat(differentSizeBrush.hashCode()).isNotEqualTo(brush.hashCode())
+
+ val differentEpsilonBrush = Brush(family, color, size, 1.1f)
+ assertThat(differentEpsilonBrush.hashCode()).isNotEqualTo(brush.hashCode())
+ }
+
+ @Test
+ fun copy_returnsTheSameBrush() {
+ val originalBrush = buildTestBrush()
+
+ val newBrush = originalBrush.copy()
+
+ // A pure copy returns `this`.
+ assertThat(newBrush).isSameInstanceAs(originalBrush)
+ }
+
+ @Test
+ fun copy_withChangedBrushFamily_returnsCopyWithDifferentBrushFamily() {
+ val originalBrush = buildTestBrush()
+
+ val newBrush = originalBrush.copy(family = BrushFamily())
+
+ assertThat(newBrush).isNotEqualTo(originalBrush)
+ assertThat(newBrush.family).isNotEqualTo(originalBrush.family)
+
+ // The new brush has the original color, size and epsilon.
+ assertThat(newBrush.colorLong).isEqualTo(originalBrush.colorLong)
+ assertThat(newBrush.size).isEqualTo(originalBrush.size)
+ assertThat(newBrush.epsilon).isEqualTo(originalBrush.epsilon)
+ }
+
+ @Test
+ fun copyWithColorIntArgb_withLowAlpha_returnsCopyWithThatColor() {
+ val originalBrush = buildTestBrush()
+
+ val newBrush = originalBrush.copyWithColorIntArgb(colorIntArgb = 0x12345678)
+
+ assertThat(newBrush).isNotEqualTo(originalBrush)
+ assertThat(newBrush.colorLong).isNotEqualTo(originalBrush.colorLong)
+ assertThat(newBrush.colorIntArgb).isEqualTo(0x12345678)
+
+ // The new brush has the original family, size and epsilon.
+ assertThat(newBrush.family).isSameInstanceAs(originalBrush.family)
+ assertThat(newBrush.size).isEqualTo(originalBrush.size)
+ assertThat(newBrush.epsilon).isEqualTo(originalBrush.epsilon)
+ }
+
+ @Test
+ fun copyWithColorIntArgb_withHighAlpha_returnsCopyWithThatColor() {
+ val originalBrush = buildTestBrush()
+
+ val newBrush = originalBrush.copyWithColorIntArgb(colorIntArgb = 0xAA123456.toInt())
+
+ assertThat(newBrush).isNotEqualTo(originalBrush)
+ assertThat(newBrush.colorLong).isNotEqualTo(originalBrush.colorLong)
+ assertThat(newBrush.colorIntArgb).isEqualTo(0xAA123456.toInt())
+
+ // The new brush has the original family, size and epsilon.
+ assertThat(newBrush.family).isSameInstanceAs(originalBrush.family)
+ assertThat(newBrush.size).isEqualTo(originalBrush.size)
+ assertThat(newBrush.epsilon).isEqualTo(originalBrush.epsilon)
+ }
+
+ @Test
+ fun copyWithColorLong_withChangedColor_returnsCopyWithThatColor() {
+ val originalBrush = buildTestBrush()
+
+ val newColor = Color(red = 255, green = 230, blue = 115, alpha = 140).value.toLong()
+ val newBrush = originalBrush.copyWithColorLong(colorLong = newColor)
+
+ assertThat(newBrush).isNotEqualTo(originalBrush)
+ assertThat(newBrush.colorLong).isNotEqualTo(originalBrush.colorLong)
+ assertThat(newBrush.colorLong).isEqualTo(newColor)
+
+ // The new brush has the original family, size and epsilon.
+ assertThat(newBrush.family).isSameInstanceAs(originalBrush.family)
+ assertThat(newBrush.size).isEqualTo(originalBrush.size)
+ assertThat(newBrush.epsilon).isEqualTo(originalBrush.epsilon)
+ }
+
+ @Test
+ fun copyWithColorLong_inUnsupportedColorSpace_returnsCopyWithConvertedColor() {
+ val originalBrush = buildTestBrush()
+
+ val newColor = Color(0.9f, 0.45f, 0.55f, 0.15f, ColorSpaces.AdobeRgb).value.toLong()
+ val newBrush = originalBrush.copyWithColorLong(colorLong = newColor)
+
+ val expectedColor = Color(newColor.toULong()).convert(ColorSpaces.DisplayP3)
+ assertThat(newBrush.colorLong).isEqualTo(expectedColor.value.toLong())
+ assertThat(newBrush.colorIntArgb).isEqualTo(expectedColor.toArgb())
+ }
+
+ @Test
+ fun brushBuilderBuild_withColorIntWithLowAlpha_createsExpectedBrush() {
+ val testBrush = buildTestBrush()
+
+ val builtBrush =
+ Brush.builder()
+ .setFamily(testBrush.family)
+ .setColorIntArgb(0x12345678)
+ .setSize(9f)
+ .setEpsilon(0.9f)
+ .build()
+
+ assertThat(builtBrush.family).isEqualTo(testBrush.family)
+ assertThat(builtBrush.colorIntArgb).isEqualTo(0x12345678)
+ assertThat(builtBrush.size).isEqualTo(9f)
+ assertThat(builtBrush.epsilon).isEqualTo(0.9f)
+ }
+
+ @Test
+ fun brushBuilderBuild_withColorIntWithHighAlpha_createsExpectedBrush() {
+ val testBrush = buildTestBrush()
+
+ val builtBrush =
+ Brush.builder()
+ .setFamily(testBrush.family)
+ .setColorIntArgb(0xAA123456.toInt())
+ .setSize(9f)
+ .setEpsilon(0.9f)
+ .build()
+
+ assertThat(builtBrush.family).isEqualTo(testBrush.family)
+ assertThat(builtBrush.colorIntArgb).isEqualTo(0xAA123456.toInt())
+ assertThat(builtBrush.size).isEqualTo(9f)
+ assertThat(builtBrush.epsilon).isEqualTo(0.9f)
+ }
+
+ @Test
+ fun brushBuilderBuild_withColorLong_createsExpectedBrush() {
+ val testBrush = buildTestBrush()
+ val testColorLong = Color(0.9f, 0.45f, 0.55f, 0.15f, ColorSpaces.DisplayP3).value.toLong()
+
+ val builtBrush =
+ Brush.builder()
+ .setFamily(testBrush.family)
+ .setColorLong(testColorLong)
+ .setSize(9f)
+ .setEpsilon(0.9f)
+ .build()
+
+ assertThat(builtBrush.family).isEqualTo(testBrush.family)
+ assertThat(builtBrush.colorLong).isEqualTo(testColorLong)
+ assertThat(builtBrush.size).isEqualTo(9f)
+ assertThat(builtBrush.epsilon).isEqualTo(0.9f)
+ }
+
+ @Test
+ fun brushBuilderBuild_withUnsupportedColorSpace_createsBrushWithConvertedColor() {
+ val testBrush = buildTestBrush()
+ val testColorLong = Color(0.9f, 0.45f, 0.55f, 0.15f, ColorSpaces.AdobeRgb).value.toLong()
+
+ val builtBrush =
+ Brush.builder()
+ .setFamily(testBrush.family)
+ .setColorLong(testColorLong)
+ .setSize(9f)
+ .setEpsilon(0.9f)
+ .build()
+
+ val expectedColor = Color(testColorLong.toULong()).convert(ColorSpaces.DisplayP3)
+ assertThat(builtBrush.family).isEqualTo(testBrush.family)
+ assertThat(builtBrush.colorLong).isEqualTo(expectedColor.value.toLong())
+ assertThat(builtBrush.size).isEqualTo(9f)
+ assertThat(builtBrush.epsilon).isEqualTo(0.9f)
+ }
+
+ /**
+ * Creates an expected C++ Brush with default brush family/color and returns true if every
+ * property of the Kotlin Brush's JNI-created C++ counterpart is equivalent to the expected C++
+ * Brush.
+ */
+ private external fun matchesDefaultBrush(
+ actualBrushNativePointer: Long
+ ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ /**
+ * Creates an expected C++ Brush with custom values and returns true if every property of the
+ * Kotlin Brush's JNI-created C++ counterpart is equivalent to the expected C++ Brush.
+ */
+ private external fun matchesCustomBrush(
+ actualBrushNativePointer: Long
+ ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+
+ /** Brush with every field different from default values. */
+ private fun buildTestBrush(): Brush =
+ Brush(
+ BrushFamily(
+ tip =
+ BrushTip(
+ 0.1f,
+ 0.2f,
+ 0.3f,
+ 0.4f,
+ 0.5f,
+ 0.6f,
+ 0.7f,
+ 0.8f,
+ 9L,
+ listOf(
+ BrushBehavior(
+ source = BrushBehavior.Source.TILT_IN_RADIANS,
+ target = BrushBehavior.Target.HEIGHT_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = 1.1f,
+ targetModifierRangeUpperBound = 1.7f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.MIRROR,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ isFallbackFor = BrushBehavior.OptionalInputProperty.TILT_X_AND_Y,
+ )
+ ),
+ ),
+ paint = BrushPaint(),
+ uri = "/brush-family:marker:1",
+ ),
+ color,
+ 13F,
+ 0.1234F,
+ )
}
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTipTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTipTest.kt
new file mode 100644
index 0000000..2e54338
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTipTest.kt
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.ink.geometry.Angle
+import com.google.common.truth.Truth.assertThat
+import kotlin.IllegalArgumentException
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalInkCustomBrushApi::class)
+@RunWith(JUnit4::class)
+class BrushTipTest {
+ /** Brush behavior with every field different from default values. */
+ private val customBehavior =
+ BrushBehavior(
+ source = BrushBehavior.Source.TILT_IN_RADIANS,
+ target = BrushBehavior.Target.HEIGHT_MULTIPLIER,
+ sourceValueRangeLowerBound = 0.2f,
+ sourceValueRangeUpperBound = .8f,
+ targetModifierRangeLowerBound = 1.1f,
+ targetModifierRangeUpperBound = 1.7f,
+ sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.MIRROR,
+ responseCurve = EasingFunction.Predefined.EASE_IN_OUT,
+ responseTimeMillis = 1L,
+ enabledToolTypes = setOf(InputToolType.STYLUS),
+ isFallbackFor = BrushBehavior.OptionalInputProperty.TILT_X_AND_Y,
+ )
+
+ @Test
+ fun constructor_returnsExpectedValues() {
+ val brushTip = BrushTip()
+ assertThat(brushTip.scaleX).isEqualTo(1f)
+ assertThat(brushTip.scaleY).isEqualTo(1f)
+ assertThat(brushTip.cornerRounding).isEqualTo(1f)
+ assertThat(brushTip.slant).isEqualTo(Angle.ZERO)
+ assertThat(brushTip.pinch).isEqualTo(0.0f)
+ assertThat(brushTip.rotation).isEqualTo(Angle.ZERO)
+ assertThat(brushTip.opacityMultiplier).isEqualTo(1.0f)
+ assertThat(brushTip.particleGapDistanceScale).isEqualTo(0.0f)
+ assertThat(brushTip.particleGapDurationMillis).isEqualTo(0L)
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun constructor_withInvalidScaleX_throws() {
+ val infinityError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(scaleX = Float.POSITIVE_INFINITY) }
+ assertThat(infinityError).hasMessageThat().contains("scale")
+ assertThat(infinityError).hasMessageThat().contains("finite")
+
+ val nanError = assertFailsWith<IllegalArgumentException> { BrushTip(scaleX = Float.NaN) }
+ assertThat(nanError).hasMessageThat().contains("scale")
+ assertThat(nanError).hasMessageThat().contains("finite")
+
+ val negativeError = assertFailsWith<IllegalArgumentException> { BrushTip(scaleX = -1.0f) }
+ assertThat(negativeError).hasMessageThat().contains("scale")
+ assertThat(negativeError).hasMessageThat().contains("non-negative")
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun constructor_withInvalidScaleY_throws() {
+ val infinityError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(scaleY = Float.POSITIVE_INFINITY) }
+ assertThat(infinityError).hasMessageThat().contains("scale")
+ assertThat(infinityError).hasMessageThat().contains("finite")
+
+ val nanError = assertFailsWith<IllegalArgumentException> { BrushTip(scaleY = Float.NaN) }
+ assertThat(nanError).hasMessageThat().contains("scale")
+ assertThat(nanError).hasMessageThat().contains("finite")
+
+ val negativeError = assertFailsWith<IllegalArgumentException> { BrushTip(scaleY = -1.0f) }
+ assertThat(negativeError).hasMessageThat().contains("scale")
+ assertThat(negativeError).hasMessageThat().contains("non-negative")
+ }
+
+ @Test
+ fun constructor_withZeroScale_throws() {
+ val zeroError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(scaleX = 0f, scaleY = 0f) }
+ assertThat(zeroError).hasMessageThat().contains("at least one value must be positive.")
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun constructor_withInvalidCornerRounding_throws() {
+ val nanError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(cornerRounding = Float.NaN) }
+ assertThat(nanError).hasMessageThat().contains("corner_rounding")
+ assertThat(nanError).hasMessageThat().contains("in the interval [0, 1]")
+
+ val lowError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(cornerRounding = -0.5f) }
+ assertThat(lowError).hasMessageThat().contains("corner_rounding")
+ assertThat(lowError).hasMessageThat().contains("in the interval [0, 1]")
+
+ val highError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(cornerRounding = 1.1f) }
+ assertThat(highError).hasMessageThat().contains("corner_rounding")
+ assertThat(highError).hasMessageThat().contains("in the interval [0, 1]")
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun constructor_withInvalidSlant_throws() {
+ val nanError = assertFailsWith<IllegalArgumentException> { BrushTip(slant = Float.NaN) }
+ assertThat(nanError).hasMessageThat().contains("slant")
+ assertThat(nanError).hasMessageThat().contains("finite")
+
+ val lowError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(slant = -Angle.HALF_TURN_RADIANS) }
+ assertThat(lowError).hasMessageThat().contains("slant")
+ assertThat(lowError).hasMessageThat().contains("interval [-pi/2, pi/2]")
+
+ val highError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(slant = Angle.HALF_TURN_RADIANS) }
+ assertThat(highError).hasMessageThat().contains("slant")
+ assertThat(highError).hasMessageThat().contains("interval [-pi/2, pi/2]")
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun constructor_withInvalidPinch_throws() {
+ val nanError = assertFailsWith<IllegalArgumentException> { BrushTip(pinch = Float.NaN) }
+ assertThat(nanError).hasMessageThat().contains("pinch")
+ assertThat(nanError).hasMessageThat().contains("interval [0, 1]")
+
+ val lowError = assertFailsWith<IllegalArgumentException> { BrushTip(pinch = -0.1f) }
+ assertThat(lowError).hasMessageThat().contains("pinch")
+ assertThat(lowError).hasMessageThat().contains("interval [0, 1]")
+
+ val highError = assertFailsWith<IllegalArgumentException> { BrushTip(pinch = 1.1f) }
+ assertThat(highError).hasMessageThat().contains("pinch")
+ assertThat(highError).hasMessageThat().contains("interval [0, 1]")
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun constructor_withInvalidOpacitiyMultiplier_throws() {
+ val nanError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(opacityMultiplier = Float.NaN) }
+ assertThat(nanError).hasMessageThat().contains("opacity_multiplier")
+ assertThat(nanError).hasMessageThat().contains("interval [0, 2]")
+
+ val lowError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(opacityMultiplier = -0.1f) }
+ assertThat(lowError).hasMessageThat().contains("opacity_multiplier")
+ assertThat(lowError).hasMessageThat().contains("interval [0, 2]")
+
+ val highError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(opacityMultiplier = 2.1f) }
+ assertThat(highError).hasMessageThat().contains("opacity_multiplier")
+ assertThat(highError).hasMessageThat().contains("interval [0, 2]")
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun constructor_withInvalidParticleGapDistanceScale_throws() {
+ val infinityError =
+ assertFailsWith<IllegalArgumentException> {
+ BrushTip(particleGapDistanceScale = Float.POSITIVE_INFINITY)
+ }
+ assertThat(infinityError).hasMessageThat().contains("particle_gap_distance_scale")
+ assertThat(infinityError).hasMessageThat().contains("finite")
+
+ val nanError =
+ assertFailsWith<IllegalArgumentException> {
+ BrushTip(particleGapDistanceScale = Float.NaN)
+ }
+ assertThat(nanError).hasMessageThat().contains("particle_gap_distance_scale")
+ assertThat(nanError).hasMessageThat().contains("finite")
+
+ val negativeError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(particleGapDistanceScale = -1.0f) }
+ assertThat(negativeError).hasMessageThat().contains("particle_gap_distance_scale")
+ assertThat(negativeError).hasMessageThat().contains("non-negative")
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun constructor_withInvalidParticleGapDurationMillis_throws() {
+ val negativeError =
+ assertFailsWith<IllegalArgumentException> { BrushTip(particleGapDurationMillis = -1L) }
+ assertThat(negativeError).hasMessageThat().contains("particle_gap_duration")
+ assertThat(negativeError).hasMessageThat().contains("non-negative")
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun constructor_withInvalidRotation_throws() {
+ val nanError = assertFailsWith<IllegalArgumentException> { BrushTip(rotation = Float.NaN) }
+ assertThat(nanError).hasMessageThat().contains("rotation")
+ assertThat(nanError).hasMessageThat().contains("finite")
+
+ val infinityError =
+ assertFailsWith<IllegalArgumentException> {
+ BrushTip(rotation = Float.POSITIVE_INFINITY)
+ }
+ assertThat(infinityError).hasMessageThat().contains("rotation")
+ assertThat(infinityError).hasMessageThat().contains("finite")
+ }
+
+ @Test
+ fun hashCode_withIdenticalValues_matches() {
+ // same values.
+ assertThat(
+ BrushTip(
+ 1f,
+ 2f,
+ 0.3f,
+ Angle.QUARTER_TURN_RADIANS,
+ 0.4f,
+ Angle.ZERO,
+ 0.7f,
+ 0.5f,
+ 100L,
+ emptyList(),
+ )
+ .hashCode()
+ )
+ .isEqualTo(
+ BrushTip(
+ 1f,
+ 2f,
+ 0.3f,
+ Angle.QUARTER_TURN_RADIANS,
+ 0.4f,
+ Angle.ZERO,
+ 0.7f,
+ 0.5f,
+ 100L,
+ emptyList(),
+ )
+ .hashCode()
+ )
+ }
+
+ @Test
+ fun equals_comparesValues() {
+ val brushTip = BrushTip()
+ // same values.
+ assertThat(brushTip).isEqualTo(BrushTip())
+
+ // different values.
+ assertThat(brushTip).isNotEqualTo(null)
+ assertThat(brushTip).isNotEqualTo(Any())
+ assertThat(brushTip).isNotEqualTo(BrushTip(scaleX = 2f))
+ assertThat(brushTip).isNotEqualTo(BrushTip(scaleY = 2f))
+ assertThat(brushTip).isNotEqualTo(BrushTip(cornerRounding = 0.2f))
+ assertThat(brushTip).isNotEqualTo(BrushTip(slant = Angle.QUARTER_TURN_RADIANS))
+ assertThat(brushTip).isNotEqualTo(BrushTip(pinch = 0.2f))
+ assertThat(brushTip).isNotEqualTo(BrushTip(rotation = Angle.HALF_TURN_RADIANS))
+ assertThat(brushTip).isNotEqualTo(BrushTip(opacityMultiplier = 0.7f))
+ assertThat(brushTip).isNotEqualTo(BrushTip(behaviors = listOf(customBehavior)))
+ }
+
+ @Test
+ fun toString_returnsExpectedValues() {
+ assertThat(BrushTip().toString())
+ .isEqualTo(
+ "BrushTip(scale=(1.0, 1.0), cornerRounding=1.0, slant=0.0, " +
+ "pinch=0.0, rotation=0.0, opacityMultiplier=1.0, " +
+ "particleGapDistanceScale=0.0, particleGapDurationMillis=0, " +
+ "behaviors=[])"
+ )
+ }
+
+ @Test
+ fun copy_withArguments_createsCopyWithChanges() {
+ val tip1 =
+ BrushTip(
+ scaleX = 2f,
+ scaleY = 3f,
+ cornerRounding = 0.5f,
+ slant = Angle.ZERO,
+ pinch = 0.5f,
+ rotation = Angle.ZERO,
+ opacityMultiplier = 0.7f,
+ particleGapDistanceScale = 0.8f,
+ particleGapDurationMillis = 9L,
+ behaviors = listOf(customBehavior),
+ )
+
+ assertThat(tip1.copy(scaleX = 3f))
+ .isEqualTo(
+ BrushTip(
+ scaleX = 3f,
+ scaleY = 3f,
+ cornerRounding = 0.5f,
+ slant = Angle.ZERO,
+ pinch = 0.5f,
+ rotation = Angle.ZERO,
+ opacityMultiplier = 0.7f,
+ particleGapDistanceScale = 0.8f,
+ particleGapDurationMillis = 9L,
+ behaviors = listOf(customBehavior),
+ )
+ )
+ }
+
+ @Test
+ fun copy_createsCopy() {
+ val tip1 =
+ BrushTip(
+ scaleX = 3f,
+ scaleY = 3f,
+ cornerRounding = 0.5f,
+ slant = Angle.ZERO,
+ pinch = 0.5f,
+ rotation = Angle.ZERO,
+ opacityMultiplier = 0.7f,
+ particleGapDistanceScale = 0.8f,
+ particleGapDurationMillis = 9L,
+ behaviors = listOf(customBehavior),
+ )
+
+ val tip2 = tip1.copy()
+
+ assertThat(tip2).isEqualTo(tip1)
+ assertThat(tip2.nativePointer).isNotEqualTo(tip1.nativePointer)
+ assertThat(tip2).isNotSameInstanceAs(tip1)
+ }
+
+ @Test
+ fun builder_createsExpectedBrushTip() {
+ val tip =
+ BrushTip.Builder()
+ .setScaleX(0.1f)
+ .setScaleY(0.2f)
+ .setCornerRounding(0.3f)
+ .setSlant(0.4f)
+ .setPinch(0.5f)
+ .setRotation(0.6f)
+ .setOpacityMultiplier(0.7f)
+ .setParticleGapDistanceScale(0.8f)
+ .setParticleGapDurationMillis(9L)
+ .setBehaviors(listOf(customBehavior))
+ .build()
+
+ assertThat(tip)
+ .isEqualTo(
+ BrushTip(0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 9L, listOf(customBehavior))
+ )
+ }
+
+ /**
+ * Creates an expected C++ BrushTip with no behaviors and returns true if every property of the
+ * Kotlin BrushTip's JNI-created C++ counterpart is equivalent to the expected C++ BrushTip.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun matchesNativeNoBehaviorTip(nativePointerToActualBrushTip: Long): Boolean
+
+ /**
+ * Creates an expected C++ BrushTip with a single behavior and returns true if every property of
+ * the Kotlin BrushTip's JNI-created C++ counterpart is equivalent to the expected C++ BrushTip.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun matchesNativeSingleBehaviorTip(
+ nativePointerToActualBrushTip: Long
+ ): Boolean
+
+ /**
+ * Creates an expected C++ BrushTip with multiple behaviors and returns true if every property
+ * of the Kotlin BrushTip's JNI-created C++ counterpart is equivalent to the expected C++
+ * BrushTip.
+ */
+ // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+ private external fun matchesNativeMultiBehaviorTip(nativePointerToActualBrushTip: Long): Boolean
+}
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/ColorExtensionsTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/ColorExtensionsTest.kt
new file mode 100644
index 0000000..e85b662
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/ColorExtensionsTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.ink.brush.color.Color as ComposeColor
+import androidx.ink.brush.color.colorspace.ColorSpaces as ComposeColorSpaces
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ColorExtensionsTest {
+ @Test
+ fun composeColorToColorInInkSupportedColorSpace_withSupportedColorSpace_returnsSameColor() {
+ val composeColor =
+ ComposeColor(
+ red = 0f,
+ green = 1f,
+ blue = 100f / 255f,
+ alpha = 155f / 255f,
+ colorSpace = ComposeColorSpaces.DisplayP3,
+ )
+
+ val convertedColor = composeColor.toColorInInkSupportedColorSpace()
+
+ // The color space is supported, so the color is the same. It's not the same instance,
+ // though,
+ // since ComposeColor is a value class.
+ assertThat(convertedColor).isEqualTo(composeColor)
+ }
+
+ @Test
+ fun composeColorToColorInInkSupportedColorSpace_withUnsupportedColorSpace_convertsToDisplayP3() {
+ val composeColor =
+ ComposeColor(
+ red = 0f,
+ green = 1f,
+ blue = 100f / 255f,
+ alpha = 155f / 255f,
+ colorSpace = ComposeColorSpaces.AdobeRgb,
+ )
+
+ val convertedColor = composeColor.toColorInInkSupportedColorSpace()
+
+ // The color space got converted to DISPLAY_P3. The color is out of gamut, so it got scaled
+ // into
+ // the Display P3 gamut.
+ assertThat(convertedColor.colorSpace).isEqualTo(ComposeColorSpaces.DisplayP3)
+ assertThat(convertedColor.red).isWithin(0.001f).of(0f)
+ assertThat(convertedColor.green).isWithin(0.001f).of(0.9795f)
+ assertThat(convertedColor.blue).isWithin(0.001f).of(0.4204f)
+ assertThat(convertedColor.alpha).isWithin(0.001f).of(155f / 255f)
+ }
+
+ @Test
+ fun composeColorSpaceToInkColorSpaceId_converts() {
+ assertThat(ComposeColorSpaces.Srgb.toInkColorSpaceId()).isEqualTo(0)
+ assertThat(ComposeColorSpaces.DisplayP3.toInkColorSpaceId()).isEqualTo(1)
+ }
+
+ @Test
+ fun composeColorSpaceToInkColorSpaceId_withUnsupportedColorSpace_throws() {
+ assertFailsWith<IllegalArgumentException> {
+ ComposeColorSpaces.AdobeRgb.toInkColorSpaceId()
+ }
+ }
+}
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/EasingFunctionTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/EasingFunctionTest.kt
new file mode 100644
index 0000000..8bf0023
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/EasingFunctionTest.kt
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import androidx.ink.brush.EasingFunction.Predefined
+import androidx.ink.geometry.ImmutableVec
+import com.google.common.truth.Truth.assertThat
+import kotlin.IllegalArgumentException
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalInkCustomBrushApi::class)
+@RunWith(JUnit4::class)
+class EasingFunctionTest {
+
+ @Test
+ fun predefinedConstants_areDistinct() {
+ val set =
+ setOf<EasingFunction.Predefined>(
+ EasingFunction.Predefined.LINEAR,
+ EasingFunction.Predefined.EASE,
+ EasingFunction.Predefined.EASE_IN,
+ EasingFunction.Predefined.EASE_OUT,
+ EasingFunction.Predefined.EASE_IN_OUT,
+ EasingFunction.Predefined.STEP_START,
+ EasingFunction.Predefined.STEP_END,
+ )
+ assertThat(set.size).isEqualTo(7)
+ }
+
+ @Test
+ fun predefinedToString_returnsCorrectString() {
+ assertThat(Predefined.LINEAR.toString()).isEqualTo("EasingFunction.Predefined.LINEAR")
+ assertThat(Predefined.EASE.toString()).isEqualTo("EasingFunction.Predefined.EASE")
+ assertThat(EasingFunction.Predefined.EASE_IN.toString())
+ .isEqualTo("EasingFunction.Predefined.EASE_IN")
+ assertThat(EasingFunction.Predefined.EASE_OUT.toString())
+ .isEqualTo("EasingFunction.Predefined.EASE_OUT")
+ assertThat(EasingFunction.Predefined.EASE_IN_OUT.toString())
+ .isEqualTo("EasingFunction.Predefined.EASE_IN_OUT")
+ assertThat(EasingFunction.Predefined.STEP_START.toString())
+ .isEqualTo("EasingFunction.Predefined.STEP_START")
+ assertThat(EasingFunction.Predefined.STEP_END.toString())
+ .isEqualTo("EasingFunction.Predefined.STEP_END")
+ }
+
+ @Test
+ fun predefinedHashCode_withIdenticalValues_matches() {
+ assertThat(EasingFunction.Predefined.LINEAR.hashCode())
+ .isEqualTo(EasingFunction.Predefined.LINEAR.hashCode())
+
+ assertThat(EasingFunction.Predefined.LINEAR.hashCode())
+ .isNotEqualTo(EasingFunction.Predefined.STEP_END.hashCode())
+ }
+
+ @Test
+ fun predefinedEquals_checksEqualityOfValues() {
+ assertThat(EasingFunction.Predefined.LINEAR).isEqualTo(EasingFunction.Predefined.LINEAR)
+ assertThat(EasingFunction.Predefined.LINEAR).isNotEqualTo(EasingFunction.Predefined.EASE)
+ assertThat(EasingFunction.Predefined.LINEAR).isNotEqualTo(null)
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun cubicBezierConstructor_requiresValuesInRange() {
+ // arg x1 outside range [0,1]
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(x1 = 1.1F, 1F, 3F, 4F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(x1 = -0.2F, 3F, 1F, 4F)
+ }
+ // arg x2 outside range [0,1]
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(1F, 3F, x2 = 2F, 4F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(1F, 3F, x2 = -0.5F, 4F)
+ }
+ }
+
+ @Test
+ @Suppress("Range") // Testing error cases.
+ fun cubicBezierConstructor_requiresFiniteValues() {
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(x1 = Float.POSITIVE_INFINITY, 1F, 1F, 1F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(x1 = Float.NaN, 1F, 1F, 1F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(1F, 1F, x2 = Float.POSITIVE_INFINITY, 1F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(1F, 1F, x2 = Float.NaN, 1F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(1F, y1 = Float.POSITIVE_INFINITY, 1F, 1F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(1F, y1 = Float.NaN, 1F, 1F)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(1F, 1F, 1F, y2 = Float.POSITIVE_INFINITY)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.CubicBezier(1F, 1F, 1F, y2 = Float.NaN)
+ }
+ }
+
+ @Test
+ fun cubicBezierHashCode_withIdenticalValues_matches() {
+ assertThat(EasingFunction.CubicBezier(1f, 2f, 0.3f, 4f).hashCode())
+ .isEqualTo(EasingFunction.CubicBezier(1f, 2f, 0.3f, 4f).hashCode())
+ }
+
+ @Test
+ fun cubicBezierEquals_checksEqualityOfValues() {
+ val original = EasingFunction.CubicBezier(1f, 2f, 0.3f, 4f)
+
+ // Equal
+ assertThat(original).isEqualTo(original) // Same instance.
+ assertThat(original).isEqualTo(EasingFunction.CubicBezier(1f, 2f, 0.3f, 4f)) // Same values.
+
+ // Not equal
+ assertThat(original).isNotEqualTo(null)
+ assertThat(original).isNotEqualTo(EasingFunction.Predefined.LINEAR) // Different type.
+ assertThat(original)
+ .isNotEqualTo(EasingFunction.CubicBezier(0.9f, 0.8f, 0.7f, 0.6f)) // Values.
+ }
+
+ @Test
+ fun cubicBezierToString_returnsReasonableString() {
+ assertThat(EasingFunction.CubicBezier(1f, 2f, 0.3f, 4f).toString())
+ .isEqualTo("EasingFunction.CubicBezier(x1=1.0, y1=2.0, x2=0.3, y2=4.0)")
+ }
+
+ @Test
+ fun linearConstructor_requiresXValuesInRange() {
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.Linear(listOf(ImmutableVec(-0.1F, 0.5F)))
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.Linear(listOf(ImmutableVec(1.1F, 0.5F)))
+ }
+ }
+
+ @Test
+ fun linearConstructor_requiresFiniteValues() {
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.Linear(listOf(ImmutableVec(Float.POSITIVE_INFINITY, 0.5F)))
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.Linear(listOf(ImmutableVec(Float.NaN, 0.5F)))
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.Linear(listOf(ImmutableVec(0.5F, Float.POSITIVE_INFINITY)))
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.Linear(listOf(ImmutableVec(0.5F, Float.NaN)))
+ }
+ }
+
+ @Test
+ fun linearConstructor_requiresSortedXValues() {
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.Linear(listOf(ImmutableVec(0.75F, 0.5F), ImmutableVec(0.25F, 0.5F)))
+ }
+ }
+
+ @Test
+ fun linearHashCode_withIdenticalValues_matches() {
+ assertThat(EasingFunction.Linear(listOf(ImmutableVec(0.25f, 0.1f))).hashCode())
+ .isEqualTo(EasingFunction.Linear(listOf(ImmutableVec(0.25f, 0.1f))).hashCode())
+ }
+
+ @Test
+ fun linearEquals_checksEqualityOfValues() {
+ val original =
+ EasingFunction.Linear(listOf(ImmutableVec(0.25f, 0.1f), ImmutableVec(0.75f, 0.9f)))
+
+ // Equal
+ assertThat(original).isEqualTo(original) // Same instance.
+ assertThat(original)
+ .isEqualTo(
+ EasingFunction.Linear(listOf(ImmutableVec(0.25f, 0.1f), ImmutableVec(0.75f, 0.9f)))
+ ) // Same values.
+
+ // Not equal
+ assertThat(original).isNotEqualTo(null)
+ assertThat(original).isNotEqualTo(EasingFunction.Predefined.LINEAR) // Different type.
+ assertThat(original)
+ .isNotEqualTo(
+ EasingFunction.Linear(listOf(ImmutableVec(0.25f, 0.1f)))
+ ) // Shorter list of points.
+ assertThat(original)
+ .isNotEqualTo(
+ EasingFunction.Linear(listOf(ImmutableVec(0.15f, 0.1f), ImmutableVec(0.75f, 0.9f)))
+ ) // Different point values.
+ assertThat(original)
+ .isNotEqualTo(
+ EasingFunction.Linear(
+ listOf(
+ ImmutableVec(0.25f, 0.1f),
+ ImmutableVec(0.75f, 0.9f),
+ ImmutableVec(0.9f, 0.5f)
+ )
+ )
+ ) // Longer list of points.
+ }
+
+ @Test
+ fun linearToString_returnsReasonableString() {
+ val string =
+ EasingFunction.Linear(listOf(ImmutableVec(0.25f, 0.1f), ImmutableVec(0.75f, 0.9f)))
+ .toString()
+ assertThat(string).contains("EasingFunction.Linear")
+ assertThat(string).contains("Vec")
+ assertThat(string).contains("0.25")
+ assertThat(string).contains("0.1")
+ assertThat(string).contains("0.75")
+ assertThat(string).contains("0.9")
+ }
+
+ @Test
+ fun stepPositionConstants_areDistinct() {
+ val set =
+ setOf<EasingFunction.StepPosition>(
+ EasingFunction.StepPosition.JUMP_START,
+ EasingFunction.StepPosition.JUMP_END,
+ EasingFunction.StepPosition.JUMP_NONE,
+ EasingFunction.StepPosition.JUMP_BOTH,
+ )
+ assertThat(set.size).isEqualTo(4)
+ }
+
+ @Test
+ fun stepPositionToString_returnsReasonableString() {
+ assertThat(EasingFunction.StepPosition.JUMP_START.toString())
+ .isEqualTo("EasingFunction.StepPosition.JUMP_START")
+ assertThat(EasingFunction.StepPosition.JUMP_END.toString())
+ .isEqualTo("EasingFunction.StepPosition.JUMP_END")
+ assertThat(EasingFunction.StepPosition.JUMP_BOTH.toString())
+ .isEqualTo("EasingFunction.StepPosition.JUMP_BOTH")
+ assertThat(EasingFunction.StepPosition.JUMP_NONE.toString())
+ .isEqualTo("EasingFunction.StepPosition.JUMP_NONE")
+ }
+
+ @Test
+ fun stepPositionHashCode_withIdenticalValues_matches() {
+ assertThat(EasingFunction.StepPosition.JUMP_START.hashCode())
+ .isEqualTo(EasingFunction.StepPosition.JUMP_START.hashCode())
+
+ assertThat(EasingFunction.StepPosition.JUMP_START.hashCode())
+ .isNotEqualTo(EasingFunction.StepPosition.JUMP_END.hashCode())
+ }
+
+ @Test
+ fun steps_withInvalidStepCount_throws() {
+ // Step count less than zero throws.
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.Steps(0, EasingFunction.StepPosition.JUMP_START)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.Steps(-1, EasingFunction.StepPosition.JUMP_START)
+ }
+
+ // Step count not greater than 1 for JUMP_NONE throws.
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.Steps(0, EasingFunction.StepPosition.JUMP_NONE)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ EasingFunction.Steps(1, EasingFunction.StepPosition.JUMP_NONE)
+ }
+
+ assertThat(EasingFunction.Steps(2, EasingFunction.StepPosition.JUMP_NONE)).isNotNull()
+ assertThat(EasingFunction.Steps(1, EasingFunction.StepPosition.JUMP_START)).isNotNull()
+ }
+
+ @Test
+ fun stepsHashCode_withSameValues_match() {
+ assertThat(EasingFunction.Steps(1, EasingFunction.StepPosition.JUMP_START).hashCode())
+ .isEqualTo(EasingFunction.Steps(1, EasingFunction.StepPosition.JUMP_START).hashCode())
+
+ // Different step count.
+ assertThat(EasingFunction.Steps(2, EasingFunction.StepPosition.JUMP_START).hashCode())
+ .isNotEqualTo(
+ EasingFunction.Steps(1, EasingFunction.StepPosition.JUMP_START).hashCode()
+ )
+
+ // Different stepPosition.
+ assertThat(EasingFunction.Steps(1, EasingFunction.StepPosition.JUMP_START).hashCode())
+ .isNotEqualTo(EasingFunction.Steps(1, EasingFunction.StepPosition.JUMP_END).hashCode())
+ }
+
+ @Test
+ fun stepsEquals_checksEqualityOfValues() {
+ val original = EasingFunction.Steps(2, EasingFunction.StepPosition.JUMP_START)
+
+ // Equal
+ assertThat(original).isEqualTo(original) // Same instance.
+ assertThat(original)
+ .isEqualTo(
+ EasingFunction.Steps(2, EasingFunction.StepPosition.JUMP_START)
+ ) // Same values.
+
+ // Not equal
+ assertThat(original).isNotEqualTo(null)
+ // Different type.
+ assertThat(original).isNotEqualTo(EasingFunction.Predefined.LINEAR)
+ // Different count.
+ assertThat(original)
+ .isNotEqualTo(EasingFunction.Steps(3, EasingFunction.StepPosition.JUMP_START))
+ // Different position.
+ assertThat(original)
+ .isNotEqualTo(EasingFunction.Steps(2, EasingFunction.StepPosition.JUMP_END))
+ }
+
+ @Test
+ fun stepsToString_returnsReasonableString() {
+ assertThat(EasingFunction.Steps(2, EasingFunction.StepPosition.JUMP_START).toString())
+ .isEqualTo(
+ "EasingFunction.Steps(stepCount=2, stepPosition=EasingFunction.StepPosition.JUMP_START)"
+ )
+ }
+}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/Empty.kt
similarity index 68%
rename from tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt
rename to ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/Empty.kt
index 3fa6028..0c09d26 100644
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/readAssetsFile.kt
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/Empty.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package androidx.tv.integration.presentation
+package androidx.ink.brush
-import android.content.res.AssetManager
-
-fun AssetManager.readAssetsFile(fileName: String): String =
- open(fileName).bufferedReader().use { it.readText() }
+/**
+ * Exists solely so that brush_test_jni_lib can have non-empty srcs, in order to aggregate its
+ * runtime_deps for convenience to the test targets.
+ */
+private class Empty
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/InputToolTypeTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/InputToolTypeTest.kt
new file mode 100644
index 0000000..209fa49
--- /dev/null
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/InputToolTypeTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 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.ink.brush
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.IllegalArgumentException
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class InputToolTypeTest {
+
+ @Test
+ fun constants_areDistinct() {
+ val set =
+ setOf(
+ InputToolType.UNKNOWN,
+ InputToolType.MOUSE,
+ InputToolType.STYLUS,
+ InputToolType.TOUCH
+ )
+ assertThat(set).hasSize(4)
+ }
+
+ @Test
+ fun toString_returnsCorrectString() {
+ assertThat(InputToolType.UNKNOWN.toString()).isEqualTo("InputToolType.UNKNOWN")
+ assertThat(InputToolType.MOUSE.toString()).isEqualTo("InputToolType.MOUSE")
+ assertThat(InputToolType.TOUCH.toString()).isEqualTo("InputToolType.TOUCH")
+ assertThat(InputToolType.STYLUS.toString()).isEqualTo("InputToolType.STYLUS")
+ }
+
+ @Test
+ fun hashCode_withIdenticalValues_matches() {
+ assertThat(InputToolType.MOUSE.hashCode()).isEqualTo(InputToolType.MOUSE.hashCode())
+
+ assertThat(InputToolType.MOUSE.hashCode()).isNotEqualTo(InputToolType.TOUCH.hashCode())
+ }
+
+ @Test
+ fun equals_checksEqualityOfValues() {
+ assertThat(InputToolType.MOUSE).isEqualTo(InputToolType.MOUSE)
+ assertThat(InputToolType.MOUSE).isNotEqualTo(InputToolType.TOUCH)
+ assertThat(InputToolType.MOUSE).isNotEqualTo(null)
+ }
+
+ @Test
+ fun from_createsCorrectInputToolType() {
+ assertThat(InputToolType.from(0)).isEqualTo(InputToolType.UNKNOWN)
+ assertThat(InputToolType.from(1)).isEqualTo(InputToolType.MOUSE)
+ assertThat(InputToolType.from(2)).isEqualTo(InputToolType.TOUCH)
+ assertThat(InputToolType.from(3)).isEqualTo(InputToolType.STYLUS)
+ assertFailsWith<IllegalArgumentException> { InputToolType.from(4) }
+ }
+}
diff --git a/ink/ink-geometry/api/current.txt b/ink/ink-geometry/api/current.txt
index 3ee8cd9..7d352f46 100644
--- a/ink/ink-geometry/api/current.txt
+++ b/ink/ink-geometry/api/current.txt
@@ -1,6 +1,22 @@
// Signature format: 4.0
package androidx.ink.geometry {
+ public abstract class AffineTransform {
+ method public final androidx.ink.geometry.MutableParallelogram applyTransform(androidx.ink.geometry.Box box, androidx.ink.geometry.MutableParallelogram outParallelogram);
+ method public final androidx.ink.geometry.MutableParallelogram applyTransform(androidx.ink.geometry.Parallelogram parallelogram, androidx.ink.geometry.MutableParallelogram outParallelogram);
+ method public final androidx.ink.geometry.MutableSegment applyTransform(androidx.ink.geometry.Segment segment, androidx.ink.geometry.MutableSegment outSegment);
+ method public final androidx.ink.geometry.MutableTriangle applyTransform(androidx.ink.geometry.Triangle triangle, androidx.ink.geometry.MutableTriangle outTriangle);
+ method public final androidx.ink.geometry.MutableVec applyTransform(androidx.ink.geometry.Vec vec, androidx.ink.geometry.MutableVec outVec);
+ method public final androidx.ink.geometry.MutableAffineTransform computeInverse(androidx.ink.geometry.MutableAffineTransform outAffineTransform);
+ method @Size(min=6L) public final float[] getValues();
+ method @Size(min=6L) public final float[] getValues(optional @Size(min=6L) float[] outArray);
+ field public static final androidx.ink.geometry.AffineTransform.Companion Companion;
+ field public static final androidx.ink.geometry.ImmutableAffineTransform IDENTITY;
+ }
+
+ public static final class AffineTransform.Companion {
+ }
+
public final class Angle {
method @androidx.ink.geometry.AngleRadiansFloat public static float degreesToRadians(@androidx.ink.geometry.AngleDegreesFloat float degrees);
method @FloatRange(from=0.0, to=androidx.ink.geometry.Angle.FULL_TURN_RADIANS_DOUBLE) @androidx.ink.geometry.AngleRadiansFloat public static float normalized(@androidx.ink.geometry.AngleRadiansFloat float radians);
@@ -19,5 +35,368 @@
@kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FIELD}) public @interface AngleRadiansFloat {
}
+ public abstract class Box {
+ method public final androidx.ink.geometry.MutableVec computeCenter(androidx.ink.geometry.MutableVec outVec);
+ method public final void computeCorners(androidx.ink.geometry.MutableVec outVecXMinYMin, androidx.ink.geometry.MutableVec outVecXMaxYMin, androidx.ink.geometry.MutableVec outVecXMaxYMax, androidx.ink.geometry.MutableVec outVecXMinYMax);
+ method public final operator boolean contains(androidx.ink.geometry.Box otherBox);
+ method public final operator boolean contains(androidx.ink.geometry.Vec point);
+ method @FloatRange(from=0.0) public final float getHeight();
+ method @FloatRange(from=0.0) public final float getWidth();
+ method public abstract float getXMax();
+ method public abstract float getXMin();
+ method public abstract float getYMax();
+ method public abstract float getYMin();
+ method public final boolean isAlmostEqual(androidx.ink.geometry.Box other, @FloatRange(from=0.0) float tolerance);
+ property @FloatRange(from=0.0) public final float height;
+ property @FloatRange(from=0.0) public final float width;
+ property public abstract float xMax;
+ property public abstract float xMin;
+ property public abstract float yMax;
+ property public abstract float yMin;
+ field public static final androidx.ink.geometry.Box.Companion Companion;
+ }
+
+ public static final class Box.Companion {
+ }
+
+ public final class BoxAccumulator {
+ ctor public BoxAccumulator();
+ ctor public BoxAccumulator(androidx.ink.geometry.Box box);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.Box? box);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.BoxAccumulator? other);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.Parallelogram parallelogram);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.Segment segment);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.Triangle triangle);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.Vec point);
+ method public androidx.ink.geometry.Box? getBox();
+ method public boolean isAlmostEqual(androidx.ink.geometry.BoxAccumulator other, @FloatRange(from=0.0) float tolerance);
+ method public boolean isEmpty();
+ method public androidx.ink.geometry.BoxAccumulator populateFrom(androidx.ink.geometry.BoxAccumulator input);
+ method public androidx.ink.geometry.BoxAccumulator reset();
+ property public final androidx.ink.geometry.Box? box;
+ }
+
+ public final class ImmutableAffineTransform extends androidx.ink.geometry.AffineTransform {
+ ctor public ImmutableAffineTransform(float m00, float m10, float m20, float m01, float m11, float m21);
+ ctor public ImmutableAffineTransform(@Size(min=6L) float[] values);
+ method public static androidx.ink.geometry.ImmutableAffineTransform scale(float scaleFactor);
+ method public static androidx.ink.geometry.ImmutableAffineTransform scale(float xScaleFactor, float yScaleFactor);
+ method public static androidx.ink.geometry.ImmutableAffineTransform scaleX(float scaleFactor);
+ method public static androidx.ink.geometry.ImmutableAffineTransform scaleY(float scaleFactor);
+ method public static androidx.ink.geometry.ImmutableAffineTransform translate(androidx.ink.geometry.Vec offset);
+ field public static final androidx.ink.geometry.ImmutableAffineTransform.Companion Companion;
+ }
+
+ public static final class ImmutableAffineTransform.Companion {
+ method public androidx.ink.geometry.ImmutableAffineTransform scale(float scaleFactor);
+ method public androidx.ink.geometry.ImmutableAffineTransform scale(float xScaleFactor, float yScaleFactor);
+ method public androidx.ink.geometry.ImmutableAffineTransform scaleX(float scaleFactor);
+ method public androidx.ink.geometry.ImmutableAffineTransform scaleY(float scaleFactor);
+ method public androidx.ink.geometry.ImmutableAffineTransform translate(androidx.ink.geometry.Vec offset);
+ }
+
+ public final class ImmutableBox extends androidx.ink.geometry.Box {
+ method public static androidx.ink.geometry.ImmutableBox fromCenterAndDimensions(androidx.ink.geometry.Vec center, @FloatRange(from=0.0) float width, @FloatRange(from=0.0) float height);
+ method public static androidx.ink.geometry.ImmutableBox fromTwoPoints(androidx.ink.geometry.Vec point1, androidx.ink.geometry.Vec point2);
+ method public float getXMax();
+ method public float getXMin();
+ method public float getYMax();
+ method public float getYMin();
+ property public float xMax;
+ property public float xMin;
+ property public float yMax;
+ property public float yMin;
+ field public static final androidx.ink.geometry.ImmutableBox.Companion Companion;
+ }
+
+ public static final class ImmutableBox.Companion {
+ method public androidx.ink.geometry.ImmutableBox fromCenterAndDimensions(androidx.ink.geometry.Vec center, @FloatRange(from=0.0) float width, @FloatRange(from=0.0) float height);
+ method public androidx.ink.geometry.ImmutableBox fromTwoPoints(androidx.ink.geometry.Vec point1, androidx.ink.geometry.Vec point2);
+ }
+
+ public final class ImmutableParallelogram extends androidx.ink.geometry.Parallelogram {
+ method public static androidx.ink.geometry.ImmutableParallelogram fromCenterAndDimensions(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height);
+ method public static androidx.ink.geometry.ImmutableParallelogram fromCenterDimensionsAndRotation(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation);
+ method public static androidx.ink.geometry.ImmutableParallelogram fromCenterDimensionsRotationAndShear(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation, float shearFactor);
+ method public androidx.ink.geometry.ImmutableVec getCenter();
+ method public float getHeight();
+ method public float getRotation();
+ method public float getShearFactor();
+ method public float getWidth();
+ property public androidx.ink.geometry.ImmutableVec center;
+ property public float height;
+ property public float rotation;
+ property public float shearFactor;
+ property public float width;
+ field public static final androidx.ink.geometry.ImmutableParallelogram.Companion Companion;
+ }
+
+ public static final class ImmutableParallelogram.Companion {
+ method public androidx.ink.geometry.ImmutableParallelogram fromCenterAndDimensions(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height);
+ method public androidx.ink.geometry.ImmutableParallelogram fromCenterDimensionsAndRotation(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation);
+ method public androidx.ink.geometry.ImmutableParallelogram fromCenterDimensionsRotationAndShear(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation, float shearFactor);
+ }
+
+ public final class ImmutableSegment extends androidx.ink.geometry.Segment {
+ ctor public ImmutableSegment(androidx.ink.geometry.Vec start, androidx.ink.geometry.Vec end);
+ method public androidx.ink.geometry.Vec getEnd();
+ method public androidx.ink.geometry.Vec getStart();
+ property public androidx.ink.geometry.Vec end;
+ property public androidx.ink.geometry.Vec start;
+ }
+
+ public final class ImmutableTriangle extends androidx.ink.geometry.Triangle {
+ ctor public ImmutableTriangle(androidx.ink.geometry.Vec p0, androidx.ink.geometry.Vec p1, androidx.ink.geometry.Vec p2);
+ method public androidx.ink.geometry.Vec getP0();
+ method public androidx.ink.geometry.Vec getP1();
+ method public androidx.ink.geometry.Vec getP2();
+ property public androidx.ink.geometry.Vec p0;
+ property public androidx.ink.geometry.Vec p1;
+ property public androidx.ink.geometry.Vec p2;
+ }
+
+ public final class ImmutableVec extends androidx.ink.geometry.Vec {
+ ctor public ImmutableVec(float x, float y);
+ method public static androidx.ink.geometry.ImmutableVec fromDirectionAndMagnitude(@androidx.ink.geometry.AngleRadiansFloat float direction, float magnitude);
+ method public float getX();
+ method public float getY();
+ property public float x;
+ property public float y;
+ field public static final androidx.ink.geometry.ImmutableVec.Companion Companion;
+ }
+
+ public static final class ImmutableVec.Companion {
+ method public androidx.ink.geometry.ImmutableVec fromDirectionAndMagnitude(@androidx.ink.geometry.AngleRadiansFloat float direction, float magnitude);
+ }
+
+ public final class Intersection {
+ method public static boolean intersects(androidx.ink.geometry.Box, androidx.ink.geometry.Box other);
+ method public static boolean intersects(androidx.ink.geometry.Box, androidx.ink.geometry.Parallelogram parallelogram);
+ method public static boolean intersects(androidx.ink.geometry.Box, androidx.ink.geometry.Segment segment);
+ method public static boolean intersects(androidx.ink.geometry.Box, androidx.ink.geometry.Triangle triangle);
+ method public static boolean intersects(androidx.ink.geometry.Box, androidx.ink.geometry.Vec point);
+ method public static boolean intersects(androidx.ink.geometry.Parallelogram, androidx.ink.geometry.Box box);
+ method public static boolean intersects(androidx.ink.geometry.Parallelogram, androidx.ink.geometry.Parallelogram other);
+ method public static boolean intersects(androidx.ink.geometry.Parallelogram, androidx.ink.geometry.Segment segment);
+ method public static boolean intersects(androidx.ink.geometry.Parallelogram, androidx.ink.geometry.Triangle triangle);
+ method public static boolean intersects(androidx.ink.geometry.Parallelogram, androidx.ink.geometry.Vec point);
+ method public static boolean intersects(androidx.ink.geometry.Segment, androidx.ink.geometry.Box box);
+ method public static boolean intersects(androidx.ink.geometry.Segment, androidx.ink.geometry.Parallelogram parallelogram);
+ method public static boolean intersects(androidx.ink.geometry.Segment, androidx.ink.geometry.Segment other);
+ method public static boolean intersects(androidx.ink.geometry.Segment, androidx.ink.geometry.Triangle triangle);
+ method public static boolean intersects(androidx.ink.geometry.Segment, androidx.ink.geometry.Vec point);
+ method public static boolean intersects(androidx.ink.geometry.Triangle, androidx.ink.geometry.Box box);
+ method public static boolean intersects(androidx.ink.geometry.Triangle, androidx.ink.geometry.Parallelogram parallelogram);
+ method public static boolean intersects(androidx.ink.geometry.Triangle, androidx.ink.geometry.Segment segment);
+ method public static boolean intersects(androidx.ink.geometry.Triangle, androidx.ink.geometry.Triangle other);
+ method public static boolean intersects(androidx.ink.geometry.Triangle, androidx.ink.geometry.Vec point);
+ method public static boolean intersects(androidx.ink.geometry.Vec, androidx.ink.geometry.Box box);
+ method public static boolean intersects(androidx.ink.geometry.Vec, androidx.ink.geometry.Parallelogram parallelogram);
+ method public static boolean intersects(androidx.ink.geometry.Vec, androidx.ink.geometry.Segment segment);
+ method public static boolean intersects(androidx.ink.geometry.Vec, androidx.ink.geometry.Triangle triangle);
+ method public static boolean intersects(androidx.ink.geometry.Vec, androidx.ink.geometry.Vec other);
+ field public static final androidx.ink.geometry.Intersection INSTANCE;
+ }
+
+ public final class MutableAffineTransform extends androidx.ink.geometry.AffineTransform {
+ ctor public MutableAffineTransform();
+ method public void setValues(float m00, float m10, float m20, float m01, float m11, float m21);
+ method public void setValues(@Size(min=6L) float[] values);
+ }
+
+ public final class MutableBox extends androidx.ink.geometry.Box {
+ ctor public MutableBox();
+ method public float getXMax();
+ method public float getXMin();
+ method public float getYMax();
+ method public float getYMin();
+ method public androidx.ink.geometry.MutableBox populateFrom(androidx.ink.geometry.Box input);
+ method public androidx.ink.geometry.MutableBox populateFromCenterAndDimensions(androidx.ink.geometry.Vec center, @FloatRange(from=0.0) float width, @FloatRange(from=0.0) float height);
+ method public androidx.ink.geometry.MutableBox populateFromTwoPoints(androidx.ink.geometry.Vec point1, androidx.ink.geometry.Vec point2);
+ method public androidx.ink.geometry.MutableBox setXBounds(float x1, float x2);
+ method public androidx.ink.geometry.MutableBox setYBounds(float y1, float y2);
+ property public float xMax;
+ property public float xMin;
+ property public float yMax;
+ property public float yMin;
+ }
+
+ public final class MutableParallelogram extends androidx.ink.geometry.Parallelogram {
+ ctor public MutableParallelogram();
+ method public static androidx.ink.geometry.MutableParallelogram fromCenterAndDimensions(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height);
+ method public static androidx.ink.geometry.MutableParallelogram fromCenterDimensionsAndRotation(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation);
+ method public static androidx.ink.geometry.MutableParallelogram fromCenterDimensionsRotationAndShear(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation, float shearFactor);
+ method public androidx.ink.geometry.MutableVec getCenter();
+ method public float getHeight();
+ method @androidx.ink.geometry.AngleRadiansFloat public float getRotation();
+ method public float getShearFactor();
+ method @FloatRange(from=0.0) public float getWidth();
+ method public void setCenter(androidx.ink.geometry.MutableVec);
+ method public void setHeight(float);
+ method public void setRotation(@androidx.ink.geometry.AngleRadiansFloat float);
+ method public void setShearFactor(float);
+ method public void setWidth(@FloatRange(from=0.0) float);
+ property public androidx.ink.geometry.MutableVec center;
+ property public float height;
+ property @androidx.ink.geometry.AngleRadiansFloat public float rotation;
+ property public float shearFactor;
+ property @FloatRange(from=0.0) public float width;
+ field public static final androidx.ink.geometry.MutableParallelogram.Companion Companion;
+ }
+
+ public static final class MutableParallelogram.Companion {
+ method public androidx.ink.geometry.MutableParallelogram fromCenterAndDimensions(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height);
+ method public androidx.ink.geometry.MutableParallelogram fromCenterDimensionsAndRotation(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation);
+ method public androidx.ink.geometry.MutableParallelogram fromCenterDimensionsRotationAndShear(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation, float shearFactor);
+ }
+
+ public final class MutableSegment extends androidx.ink.geometry.Segment {
+ ctor public MutableSegment();
+ ctor public MutableSegment(androidx.ink.geometry.MutableVec start, androidx.ink.geometry.MutableVec end);
+ method public androidx.ink.geometry.MutableVec getEnd();
+ method public androidx.ink.geometry.MutableVec getStart();
+ method public androidx.ink.geometry.MutableSegment populateFrom(androidx.ink.geometry.Segment input);
+ method public void setEnd(androidx.ink.geometry.MutableVec);
+ method public void setStart(androidx.ink.geometry.MutableVec);
+ property public androidx.ink.geometry.MutableVec end;
+ property public androidx.ink.geometry.MutableVec start;
+ }
+
+ public final class MutableTriangle extends androidx.ink.geometry.Triangle {
+ ctor public MutableTriangle();
+ ctor public MutableTriangle(androidx.ink.geometry.MutableVec p0, androidx.ink.geometry.MutableVec p1, androidx.ink.geometry.MutableVec p2);
+ method public androidx.ink.geometry.MutableVec getP0();
+ method public androidx.ink.geometry.MutableVec getP1();
+ method public androidx.ink.geometry.MutableVec getP2();
+ method public androidx.ink.geometry.MutableTriangle populateFrom(androidx.ink.geometry.Triangle input);
+ method public void setP0(androidx.ink.geometry.MutableVec);
+ method public void setP1(androidx.ink.geometry.MutableVec);
+ method public void setP2(androidx.ink.geometry.MutableVec);
+ property public androidx.ink.geometry.MutableVec p0;
+ property public androidx.ink.geometry.MutableVec p1;
+ property public androidx.ink.geometry.MutableVec p2;
+ }
+
+ public final class MutableVec extends androidx.ink.geometry.Vec {
+ ctor public MutableVec();
+ ctor public MutableVec(float x, float y);
+ method public static androidx.ink.geometry.MutableVec fromDirectionAndMagnitude(@androidx.ink.geometry.AngleRadiansFloat float direction, float magnitude);
+ method public float getX();
+ method public float getY();
+ method public androidx.ink.geometry.MutableVec populateFrom(androidx.ink.geometry.Vec input);
+ method public void setX(float);
+ method public void setY(float);
+ property public float x;
+ property public float y;
+ field public static final androidx.ink.geometry.MutableVec.Companion Companion;
+ }
+
+ public static final class MutableVec.Companion {
+ method public androidx.ink.geometry.MutableVec fromDirectionAndMagnitude(@androidx.ink.geometry.AngleRadiansFloat float direction, float magnitude);
+ }
+
+ public abstract class Parallelogram {
+ method public final float computeSignedArea();
+ method public abstract androidx.ink.geometry.Vec getCenter();
+ method public abstract float getHeight();
+ method @androidx.ink.geometry.AngleRadiansFloat public abstract float getRotation();
+ method public abstract float getShearFactor();
+ method @FloatRange(from=0.0) public abstract float getWidth();
+ property public abstract androidx.ink.geometry.Vec center;
+ property public abstract float height;
+ property @androidx.ink.geometry.AngleRadiansFloat public abstract float rotation;
+ property public abstract float shearFactor;
+ property @FloatRange(from=0.0) public abstract float width;
+ field public static final androidx.ink.geometry.Parallelogram.Companion Companion;
+ }
+
+ public static final class Parallelogram.Companion {
+ }
+
+ public abstract class Segment {
+ method public final androidx.ink.geometry.ImmutableBox computeBoundingBox();
+ method public final androidx.ink.geometry.MutableBox computeBoundingBox(androidx.ink.geometry.MutableBox outBox);
+ method public final androidx.ink.geometry.ImmutableVec computeDisplacement();
+ method public final androidx.ink.geometry.MutableVec computeDisplacement(androidx.ink.geometry.MutableVec outVec);
+ method @FloatRange(from=0.0) public final float computeLength();
+ method public final androidx.ink.geometry.ImmutableVec computeLerpPoint(float ratio);
+ method public final androidx.ink.geometry.MutableVec computeLerpPoint(float ratio, androidx.ink.geometry.MutableVec outVec);
+ method public final androidx.ink.geometry.ImmutableVec computeMidpoint();
+ method public final androidx.ink.geometry.MutableVec computeMidpoint(androidx.ink.geometry.MutableVec outVec);
+ method public abstract androidx.ink.geometry.Vec getEnd();
+ method public abstract androidx.ink.geometry.Vec getStart();
+ method public final boolean isAlmostEqual(androidx.ink.geometry.Segment other, @FloatRange(from=0.0) float tolerance);
+ method public final float project(androidx.ink.geometry.Vec pointToProject);
+ property public abstract androidx.ink.geometry.Vec end;
+ property public abstract androidx.ink.geometry.Vec start;
+ field public static final androidx.ink.geometry.Segment.Companion Companion;
+ }
+
+ public static final class Segment.Companion {
+ }
+
+ public abstract class Triangle {
+ method public final androidx.ink.geometry.ImmutableBox computeBoundingBox();
+ method public final androidx.ink.geometry.MutableBox computeBoundingBox(androidx.ink.geometry.MutableBox outBox);
+ method public final androidx.ink.geometry.ImmutableSegment computeEdge(@IntRange(from=0L, to=2L) int index);
+ method public final androidx.ink.geometry.MutableSegment computeEdge(@IntRange(from=0L, to=2L) int index, androidx.ink.geometry.MutableSegment outSegment);
+ method public final float computeSignedArea();
+ method public final operator boolean contains(androidx.ink.geometry.Vec point);
+ method public abstract androidx.ink.geometry.Vec getP0();
+ method public abstract androidx.ink.geometry.Vec getP1();
+ method public abstract androidx.ink.geometry.Vec getP2();
+ method public final boolean isAlmostEqual(androidx.ink.geometry.Triangle other, @FloatRange(from=0.0) float tolerance);
+ property public abstract androidx.ink.geometry.Vec p0;
+ property public abstract androidx.ink.geometry.Vec p1;
+ property public abstract androidx.ink.geometry.Vec p2;
+ field public static final androidx.ink.geometry.Triangle.Companion Companion;
+ }
+
+ public static final class Triangle.Companion {
+ }
+
+ public abstract class Vec {
+ method @FloatRange(from=0.0, to=java.lang.Math.PI) @androidx.ink.geometry.AngleRadiansFloat public static final float absoluteAngleBetween(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public static final void add(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ method @FloatRange(from=-3.141592653589793, to=java.lang.Math.PI) @androidx.ink.geometry.AngleRadiansFloat public final float computeDirection();
+ method @FloatRange(from=0.0) public final float computeMagnitude();
+ method @FloatRange(from=0.0) public final float computeMagnitudeSquared();
+ method public final androidx.ink.geometry.ImmutableVec computeNegation();
+ method public final androidx.ink.geometry.MutableVec computeNegation(androidx.ink.geometry.MutableVec outVec);
+ method public final androidx.ink.geometry.ImmutableVec computeOrthogonal();
+ method public final androidx.ink.geometry.MutableVec computeOrthogonal(androidx.ink.geometry.MutableVec outVec);
+ method public final androidx.ink.geometry.ImmutableVec computeUnitVec();
+ method public final androidx.ink.geometry.MutableVec computeUnitVec(androidx.ink.geometry.MutableVec outVec);
+ method public static final float determinant(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public static final void divide(androidx.ink.geometry.Vec lhs, float rhs, androidx.ink.geometry.MutableVec output);
+ method public static final float dotProduct(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public abstract float getX();
+ method public abstract float getY();
+ method public final boolean isAlmostEqual(androidx.ink.geometry.Vec other);
+ method public final boolean isAlmostEqual(androidx.ink.geometry.Vec other, optional @FloatRange(from=0.0) float tolerance);
+ method public final boolean isParallelTo(androidx.ink.geometry.Vec other, @FloatRange(from=0.0) @androidx.ink.geometry.AngleRadiansFloat float angleTolerance);
+ method public final boolean isPerpendicularTo(androidx.ink.geometry.Vec other, @FloatRange(from=0.0) @androidx.ink.geometry.AngleRadiansFloat float angleTolerance);
+ method public static final void multiply(androidx.ink.geometry.Vec lhs, float rhs, androidx.ink.geometry.MutableVec output);
+ method public static final void multiply(float lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ method @FloatRange(from=-3.141592653589793, to=java.lang.Math.PI, fromInclusive=false) @androidx.ink.geometry.AngleRadiansFloat public static final float signedAngleBetween(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public static final void subtract(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ property public abstract float x;
+ property public abstract float y;
+ field public static final androidx.ink.geometry.Vec.Companion Companion;
+ field public static final androidx.ink.geometry.ImmutableVec ORIGIN;
+ }
+
+ public static final class Vec.Companion {
+ method @FloatRange(from=0.0, to=java.lang.Math.PI) @androidx.ink.geometry.AngleRadiansFloat public float absoluteAngleBetween(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public void add(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ method public float determinant(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public void divide(androidx.ink.geometry.Vec lhs, float rhs, androidx.ink.geometry.MutableVec output);
+ method public float dotProduct(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public void multiply(androidx.ink.geometry.Vec lhs, float rhs, androidx.ink.geometry.MutableVec output);
+ method public void multiply(float lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ method @FloatRange(from=-3.141592653589793, to=java.lang.Math.PI, fromInclusive=false) @androidx.ink.geometry.AngleRadiansFloat public float signedAngleBetween(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public void subtract(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ }
+
}
diff --git a/ink/ink-geometry/api/restricted_current.txt b/ink/ink-geometry/api/restricted_current.txt
index 3ee8cd9..7d352f46 100644
--- a/ink/ink-geometry/api/restricted_current.txt
+++ b/ink/ink-geometry/api/restricted_current.txt
@@ -1,6 +1,22 @@
// Signature format: 4.0
package androidx.ink.geometry {
+ public abstract class AffineTransform {
+ method public final androidx.ink.geometry.MutableParallelogram applyTransform(androidx.ink.geometry.Box box, androidx.ink.geometry.MutableParallelogram outParallelogram);
+ method public final androidx.ink.geometry.MutableParallelogram applyTransform(androidx.ink.geometry.Parallelogram parallelogram, androidx.ink.geometry.MutableParallelogram outParallelogram);
+ method public final androidx.ink.geometry.MutableSegment applyTransform(androidx.ink.geometry.Segment segment, androidx.ink.geometry.MutableSegment outSegment);
+ method public final androidx.ink.geometry.MutableTriangle applyTransform(androidx.ink.geometry.Triangle triangle, androidx.ink.geometry.MutableTriangle outTriangle);
+ method public final androidx.ink.geometry.MutableVec applyTransform(androidx.ink.geometry.Vec vec, androidx.ink.geometry.MutableVec outVec);
+ method public final androidx.ink.geometry.MutableAffineTransform computeInverse(androidx.ink.geometry.MutableAffineTransform outAffineTransform);
+ method @Size(min=6L) public final float[] getValues();
+ method @Size(min=6L) public final float[] getValues(optional @Size(min=6L) float[] outArray);
+ field public static final androidx.ink.geometry.AffineTransform.Companion Companion;
+ field public static final androidx.ink.geometry.ImmutableAffineTransform IDENTITY;
+ }
+
+ public static final class AffineTransform.Companion {
+ }
+
public final class Angle {
method @androidx.ink.geometry.AngleRadiansFloat public static float degreesToRadians(@androidx.ink.geometry.AngleDegreesFloat float degrees);
method @FloatRange(from=0.0, to=androidx.ink.geometry.Angle.FULL_TURN_RADIANS_DOUBLE) @androidx.ink.geometry.AngleRadiansFloat public static float normalized(@androidx.ink.geometry.AngleRadiansFloat float radians);
@@ -19,5 +35,368 @@
@kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FIELD}) public @interface AngleRadiansFloat {
}
+ public abstract class Box {
+ method public final androidx.ink.geometry.MutableVec computeCenter(androidx.ink.geometry.MutableVec outVec);
+ method public final void computeCorners(androidx.ink.geometry.MutableVec outVecXMinYMin, androidx.ink.geometry.MutableVec outVecXMaxYMin, androidx.ink.geometry.MutableVec outVecXMaxYMax, androidx.ink.geometry.MutableVec outVecXMinYMax);
+ method public final operator boolean contains(androidx.ink.geometry.Box otherBox);
+ method public final operator boolean contains(androidx.ink.geometry.Vec point);
+ method @FloatRange(from=0.0) public final float getHeight();
+ method @FloatRange(from=0.0) public final float getWidth();
+ method public abstract float getXMax();
+ method public abstract float getXMin();
+ method public abstract float getYMax();
+ method public abstract float getYMin();
+ method public final boolean isAlmostEqual(androidx.ink.geometry.Box other, @FloatRange(from=0.0) float tolerance);
+ property @FloatRange(from=0.0) public final float height;
+ property @FloatRange(from=0.0) public final float width;
+ property public abstract float xMax;
+ property public abstract float xMin;
+ property public abstract float yMax;
+ property public abstract float yMin;
+ field public static final androidx.ink.geometry.Box.Companion Companion;
+ }
+
+ public static final class Box.Companion {
+ }
+
+ public final class BoxAccumulator {
+ ctor public BoxAccumulator();
+ ctor public BoxAccumulator(androidx.ink.geometry.Box box);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.Box? box);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.BoxAccumulator? other);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.Parallelogram parallelogram);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.Segment segment);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.Triangle triangle);
+ method public androidx.ink.geometry.BoxAccumulator add(androidx.ink.geometry.Vec point);
+ method public androidx.ink.geometry.Box? getBox();
+ method public boolean isAlmostEqual(androidx.ink.geometry.BoxAccumulator other, @FloatRange(from=0.0) float tolerance);
+ method public boolean isEmpty();
+ method public androidx.ink.geometry.BoxAccumulator populateFrom(androidx.ink.geometry.BoxAccumulator input);
+ method public androidx.ink.geometry.BoxAccumulator reset();
+ property public final androidx.ink.geometry.Box? box;
+ }
+
+ public final class ImmutableAffineTransform extends androidx.ink.geometry.AffineTransform {
+ ctor public ImmutableAffineTransform(float m00, float m10, float m20, float m01, float m11, float m21);
+ ctor public ImmutableAffineTransform(@Size(min=6L) float[] values);
+ method public static androidx.ink.geometry.ImmutableAffineTransform scale(float scaleFactor);
+ method public static androidx.ink.geometry.ImmutableAffineTransform scale(float xScaleFactor, float yScaleFactor);
+ method public static androidx.ink.geometry.ImmutableAffineTransform scaleX(float scaleFactor);
+ method public static androidx.ink.geometry.ImmutableAffineTransform scaleY(float scaleFactor);
+ method public static androidx.ink.geometry.ImmutableAffineTransform translate(androidx.ink.geometry.Vec offset);
+ field public static final androidx.ink.geometry.ImmutableAffineTransform.Companion Companion;
+ }
+
+ public static final class ImmutableAffineTransform.Companion {
+ method public androidx.ink.geometry.ImmutableAffineTransform scale(float scaleFactor);
+ method public androidx.ink.geometry.ImmutableAffineTransform scale(float xScaleFactor, float yScaleFactor);
+ method public androidx.ink.geometry.ImmutableAffineTransform scaleX(float scaleFactor);
+ method public androidx.ink.geometry.ImmutableAffineTransform scaleY(float scaleFactor);
+ method public androidx.ink.geometry.ImmutableAffineTransform translate(androidx.ink.geometry.Vec offset);
+ }
+
+ public final class ImmutableBox extends androidx.ink.geometry.Box {
+ method public static androidx.ink.geometry.ImmutableBox fromCenterAndDimensions(androidx.ink.geometry.Vec center, @FloatRange(from=0.0) float width, @FloatRange(from=0.0) float height);
+ method public static androidx.ink.geometry.ImmutableBox fromTwoPoints(androidx.ink.geometry.Vec point1, androidx.ink.geometry.Vec point2);
+ method public float getXMax();
+ method public float getXMin();
+ method public float getYMax();
+ method public float getYMin();
+ property public float xMax;
+ property public float xMin;
+ property public float yMax;
+ property public float yMin;
+ field public static final androidx.ink.geometry.ImmutableBox.Companion Companion;
+ }
+
+ public static final class ImmutableBox.Companion {
+ method public androidx.ink.geometry.ImmutableBox fromCenterAndDimensions(androidx.ink.geometry.Vec center, @FloatRange(from=0.0) float width, @FloatRange(from=0.0) float height);
+ method public androidx.ink.geometry.ImmutableBox fromTwoPoints(androidx.ink.geometry.Vec point1, androidx.ink.geometry.Vec point2);
+ }
+
+ public final class ImmutableParallelogram extends androidx.ink.geometry.Parallelogram {
+ method public static androidx.ink.geometry.ImmutableParallelogram fromCenterAndDimensions(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height);
+ method public static androidx.ink.geometry.ImmutableParallelogram fromCenterDimensionsAndRotation(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation);
+ method public static androidx.ink.geometry.ImmutableParallelogram fromCenterDimensionsRotationAndShear(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation, float shearFactor);
+ method public androidx.ink.geometry.ImmutableVec getCenter();
+ method public float getHeight();
+ method public float getRotation();
+ method public float getShearFactor();
+ method public float getWidth();
+ property public androidx.ink.geometry.ImmutableVec center;
+ property public float height;
+ property public float rotation;
+ property public float shearFactor;
+ property public float width;
+ field public static final androidx.ink.geometry.ImmutableParallelogram.Companion Companion;
+ }
+
+ public static final class ImmutableParallelogram.Companion {
+ method public androidx.ink.geometry.ImmutableParallelogram fromCenterAndDimensions(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height);
+ method public androidx.ink.geometry.ImmutableParallelogram fromCenterDimensionsAndRotation(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation);
+ method public androidx.ink.geometry.ImmutableParallelogram fromCenterDimensionsRotationAndShear(androidx.ink.geometry.ImmutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation, float shearFactor);
+ }
+
+ public final class ImmutableSegment extends androidx.ink.geometry.Segment {
+ ctor public ImmutableSegment(androidx.ink.geometry.Vec start, androidx.ink.geometry.Vec end);
+ method public androidx.ink.geometry.Vec getEnd();
+ method public androidx.ink.geometry.Vec getStart();
+ property public androidx.ink.geometry.Vec end;
+ property public androidx.ink.geometry.Vec start;
+ }
+
+ public final class ImmutableTriangle extends androidx.ink.geometry.Triangle {
+ ctor public ImmutableTriangle(androidx.ink.geometry.Vec p0, androidx.ink.geometry.Vec p1, androidx.ink.geometry.Vec p2);
+ method public androidx.ink.geometry.Vec getP0();
+ method public androidx.ink.geometry.Vec getP1();
+ method public androidx.ink.geometry.Vec getP2();
+ property public androidx.ink.geometry.Vec p0;
+ property public androidx.ink.geometry.Vec p1;
+ property public androidx.ink.geometry.Vec p2;
+ }
+
+ public final class ImmutableVec extends androidx.ink.geometry.Vec {
+ ctor public ImmutableVec(float x, float y);
+ method public static androidx.ink.geometry.ImmutableVec fromDirectionAndMagnitude(@androidx.ink.geometry.AngleRadiansFloat float direction, float magnitude);
+ method public float getX();
+ method public float getY();
+ property public float x;
+ property public float y;
+ field public static final androidx.ink.geometry.ImmutableVec.Companion Companion;
+ }
+
+ public static final class ImmutableVec.Companion {
+ method public androidx.ink.geometry.ImmutableVec fromDirectionAndMagnitude(@androidx.ink.geometry.AngleRadiansFloat float direction, float magnitude);
+ }
+
+ public final class Intersection {
+ method public static boolean intersects(androidx.ink.geometry.Box, androidx.ink.geometry.Box other);
+ method public static boolean intersects(androidx.ink.geometry.Box, androidx.ink.geometry.Parallelogram parallelogram);
+ method public static boolean intersects(androidx.ink.geometry.Box, androidx.ink.geometry.Segment segment);
+ method public static boolean intersects(androidx.ink.geometry.Box, androidx.ink.geometry.Triangle triangle);
+ method public static boolean intersects(androidx.ink.geometry.Box, androidx.ink.geometry.Vec point);
+ method public static boolean intersects(androidx.ink.geometry.Parallelogram, androidx.ink.geometry.Box box);
+ method public static boolean intersects(androidx.ink.geometry.Parallelogram, androidx.ink.geometry.Parallelogram other);
+ method public static boolean intersects(androidx.ink.geometry.Parallelogram, androidx.ink.geometry.Segment segment);
+ method public static boolean intersects(androidx.ink.geometry.Parallelogram, androidx.ink.geometry.Triangle triangle);
+ method public static boolean intersects(androidx.ink.geometry.Parallelogram, androidx.ink.geometry.Vec point);
+ method public static boolean intersects(androidx.ink.geometry.Segment, androidx.ink.geometry.Box box);
+ method public static boolean intersects(androidx.ink.geometry.Segment, androidx.ink.geometry.Parallelogram parallelogram);
+ method public static boolean intersects(androidx.ink.geometry.Segment, androidx.ink.geometry.Segment other);
+ method public static boolean intersects(androidx.ink.geometry.Segment, androidx.ink.geometry.Triangle triangle);
+ method public static boolean intersects(androidx.ink.geometry.Segment, androidx.ink.geometry.Vec point);
+ method public static boolean intersects(androidx.ink.geometry.Triangle, androidx.ink.geometry.Box box);
+ method public static boolean intersects(androidx.ink.geometry.Triangle, androidx.ink.geometry.Parallelogram parallelogram);
+ method public static boolean intersects(androidx.ink.geometry.Triangle, androidx.ink.geometry.Segment segment);
+ method public static boolean intersects(androidx.ink.geometry.Triangle, androidx.ink.geometry.Triangle other);
+ method public static boolean intersects(androidx.ink.geometry.Triangle, androidx.ink.geometry.Vec point);
+ method public static boolean intersects(androidx.ink.geometry.Vec, androidx.ink.geometry.Box box);
+ method public static boolean intersects(androidx.ink.geometry.Vec, androidx.ink.geometry.Parallelogram parallelogram);
+ method public static boolean intersects(androidx.ink.geometry.Vec, androidx.ink.geometry.Segment segment);
+ method public static boolean intersects(androidx.ink.geometry.Vec, androidx.ink.geometry.Triangle triangle);
+ method public static boolean intersects(androidx.ink.geometry.Vec, androidx.ink.geometry.Vec other);
+ field public static final androidx.ink.geometry.Intersection INSTANCE;
+ }
+
+ public final class MutableAffineTransform extends androidx.ink.geometry.AffineTransform {
+ ctor public MutableAffineTransform();
+ method public void setValues(float m00, float m10, float m20, float m01, float m11, float m21);
+ method public void setValues(@Size(min=6L) float[] values);
+ }
+
+ public final class MutableBox extends androidx.ink.geometry.Box {
+ ctor public MutableBox();
+ method public float getXMax();
+ method public float getXMin();
+ method public float getYMax();
+ method public float getYMin();
+ method public androidx.ink.geometry.MutableBox populateFrom(androidx.ink.geometry.Box input);
+ method public androidx.ink.geometry.MutableBox populateFromCenterAndDimensions(androidx.ink.geometry.Vec center, @FloatRange(from=0.0) float width, @FloatRange(from=0.0) float height);
+ method public androidx.ink.geometry.MutableBox populateFromTwoPoints(androidx.ink.geometry.Vec point1, androidx.ink.geometry.Vec point2);
+ method public androidx.ink.geometry.MutableBox setXBounds(float x1, float x2);
+ method public androidx.ink.geometry.MutableBox setYBounds(float y1, float y2);
+ property public float xMax;
+ property public float xMin;
+ property public float yMax;
+ property public float yMin;
+ }
+
+ public final class MutableParallelogram extends androidx.ink.geometry.Parallelogram {
+ ctor public MutableParallelogram();
+ method public static androidx.ink.geometry.MutableParallelogram fromCenterAndDimensions(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height);
+ method public static androidx.ink.geometry.MutableParallelogram fromCenterDimensionsAndRotation(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation);
+ method public static androidx.ink.geometry.MutableParallelogram fromCenterDimensionsRotationAndShear(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation, float shearFactor);
+ method public androidx.ink.geometry.MutableVec getCenter();
+ method public float getHeight();
+ method @androidx.ink.geometry.AngleRadiansFloat public float getRotation();
+ method public float getShearFactor();
+ method @FloatRange(from=0.0) public float getWidth();
+ method public void setCenter(androidx.ink.geometry.MutableVec);
+ method public void setHeight(float);
+ method public void setRotation(@androidx.ink.geometry.AngleRadiansFloat float);
+ method public void setShearFactor(float);
+ method public void setWidth(@FloatRange(from=0.0) float);
+ property public androidx.ink.geometry.MutableVec center;
+ property public float height;
+ property @androidx.ink.geometry.AngleRadiansFloat public float rotation;
+ property public float shearFactor;
+ property @FloatRange(from=0.0) public float width;
+ field public static final androidx.ink.geometry.MutableParallelogram.Companion Companion;
+ }
+
+ public static final class MutableParallelogram.Companion {
+ method public androidx.ink.geometry.MutableParallelogram fromCenterAndDimensions(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height);
+ method public androidx.ink.geometry.MutableParallelogram fromCenterDimensionsAndRotation(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation);
+ method public androidx.ink.geometry.MutableParallelogram fromCenterDimensionsRotationAndShear(androidx.ink.geometry.MutableVec center, @FloatRange(from=0.0) float width, float height, @androidx.ink.geometry.AngleRadiansFloat float rotation, float shearFactor);
+ }
+
+ public final class MutableSegment extends androidx.ink.geometry.Segment {
+ ctor public MutableSegment();
+ ctor public MutableSegment(androidx.ink.geometry.MutableVec start, androidx.ink.geometry.MutableVec end);
+ method public androidx.ink.geometry.MutableVec getEnd();
+ method public androidx.ink.geometry.MutableVec getStart();
+ method public androidx.ink.geometry.MutableSegment populateFrom(androidx.ink.geometry.Segment input);
+ method public void setEnd(androidx.ink.geometry.MutableVec);
+ method public void setStart(androidx.ink.geometry.MutableVec);
+ property public androidx.ink.geometry.MutableVec end;
+ property public androidx.ink.geometry.MutableVec start;
+ }
+
+ public final class MutableTriangle extends androidx.ink.geometry.Triangle {
+ ctor public MutableTriangle();
+ ctor public MutableTriangle(androidx.ink.geometry.MutableVec p0, androidx.ink.geometry.MutableVec p1, androidx.ink.geometry.MutableVec p2);
+ method public androidx.ink.geometry.MutableVec getP0();
+ method public androidx.ink.geometry.MutableVec getP1();
+ method public androidx.ink.geometry.MutableVec getP2();
+ method public androidx.ink.geometry.MutableTriangle populateFrom(androidx.ink.geometry.Triangle input);
+ method public void setP0(androidx.ink.geometry.MutableVec);
+ method public void setP1(androidx.ink.geometry.MutableVec);
+ method public void setP2(androidx.ink.geometry.MutableVec);
+ property public androidx.ink.geometry.MutableVec p0;
+ property public androidx.ink.geometry.MutableVec p1;
+ property public androidx.ink.geometry.MutableVec p2;
+ }
+
+ public final class MutableVec extends androidx.ink.geometry.Vec {
+ ctor public MutableVec();
+ ctor public MutableVec(float x, float y);
+ method public static androidx.ink.geometry.MutableVec fromDirectionAndMagnitude(@androidx.ink.geometry.AngleRadiansFloat float direction, float magnitude);
+ method public float getX();
+ method public float getY();
+ method public androidx.ink.geometry.MutableVec populateFrom(androidx.ink.geometry.Vec input);
+ method public void setX(float);
+ method public void setY(float);
+ property public float x;
+ property public float y;
+ field public static final androidx.ink.geometry.MutableVec.Companion Companion;
+ }
+
+ public static final class MutableVec.Companion {
+ method public androidx.ink.geometry.MutableVec fromDirectionAndMagnitude(@androidx.ink.geometry.AngleRadiansFloat float direction, float magnitude);
+ }
+
+ public abstract class Parallelogram {
+ method public final float computeSignedArea();
+ method public abstract androidx.ink.geometry.Vec getCenter();
+ method public abstract float getHeight();
+ method @androidx.ink.geometry.AngleRadiansFloat public abstract float getRotation();
+ method public abstract float getShearFactor();
+ method @FloatRange(from=0.0) public abstract float getWidth();
+ property public abstract androidx.ink.geometry.Vec center;
+ property public abstract float height;
+ property @androidx.ink.geometry.AngleRadiansFloat public abstract float rotation;
+ property public abstract float shearFactor;
+ property @FloatRange(from=0.0) public abstract float width;
+ field public static final androidx.ink.geometry.Parallelogram.Companion Companion;
+ }
+
+ public static final class Parallelogram.Companion {
+ }
+
+ public abstract class Segment {
+ method public final androidx.ink.geometry.ImmutableBox computeBoundingBox();
+ method public final androidx.ink.geometry.MutableBox computeBoundingBox(androidx.ink.geometry.MutableBox outBox);
+ method public final androidx.ink.geometry.ImmutableVec computeDisplacement();
+ method public final androidx.ink.geometry.MutableVec computeDisplacement(androidx.ink.geometry.MutableVec outVec);
+ method @FloatRange(from=0.0) public final float computeLength();
+ method public final androidx.ink.geometry.ImmutableVec computeLerpPoint(float ratio);
+ method public final androidx.ink.geometry.MutableVec computeLerpPoint(float ratio, androidx.ink.geometry.MutableVec outVec);
+ method public final androidx.ink.geometry.ImmutableVec computeMidpoint();
+ method public final androidx.ink.geometry.MutableVec computeMidpoint(androidx.ink.geometry.MutableVec outVec);
+ method public abstract androidx.ink.geometry.Vec getEnd();
+ method public abstract androidx.ink.geometry.Vec getStart();
+ method public final boolean isAlmostEqual(androidx.ink.geometry.Segment other, @FloatRange(from=0.0) float tolerance);
+ method public final float project(androidx.ink.geometry.Vec pointToProject);
+ property public abstract androidx.ink.geometry.Vec end;
+ property public abstract androidx.ink.geometry.Vec start;
+ field public static final androidx.ink.geometry.Segment.Companion Companion;
+ }
+
+ public static final class Segment.Companion {
+ }
+
+ public abstract class Triangle {
+ method public final androidx.ink.geometry.ImmutableBox computeBoundingBox();
+ method public final androidx.ink.geometry.MutableBox computeBoundingBox(androidx.ink.geometry.MutableBox outBox);
+ method public final androidx.ink.geometry.ImmutableSegment computeEdge(@IntRange(from=0L, to=2L) int index);
+ method public final androidx.ink.geometry.MutableSegment computeEdge(@IntRange(from=0L, to=2L) int index, androidx.ink.geometry.MutableSegment outSegment);
+ method public final float computeSignedArea();
+ method public final operator boolean contains(androidx.ink.geometry.Vec point);
+ method public abstract androidx.ink.geometry.Vec getP0();
+ method public abstract androidx.ink.geometry.Vec getP1();
+ method public abstract androidx.ink.geometry.Vec getP2();
+ method public final boolean isAlmostEqual(androidx.ink.geometry.Triangle other, @FloatRange(from=0.0) float tolerance);
+ property public abstract androidx.ink.geometry.Vec p0;
+ property public abstract androidx.ink.geometry.Vec p1;
+ property public abstract androidx.ink.geometry.Vec p2;
+ field public static final androidx.ink.geometry.Triangle.Companion Companion;
+ }
+
+ public static final class Triangle.Companion {
+ }
+
+ public abstract class Vec {
+ method @FloatRange(from=0.0, to=java.lang.Math.PI) @androidx.ink.geometry.AngleRadiansFloat public static final float absoluteAngleBetween(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public static final void add(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ method @FloatRange(from=-3.141592653589793, to=java.lang.Math.PI) @androidx.ink.geometry.AngleRadiansFloat public final float computeDirection();
+ method @FloatRange(from=0.0) public final float computeMagnitude();
+ method @FloatRange(from=0.0) public final float computeMagnitudeSquared();
+ method public final androidx.ink.geometry.ImmutableVec computeNegation();
+ method public final androidx.ink.geometry.MutableVec computeNegation(androidx.ink.geometry.MutableVec outVec);
+ method public final androidx.ink.geometry.ImmutableVec computeOrthogonal();
+ method public final androidx.ink.geometry.MutableVec computeOrthogonal(androidx.ink.geometry.MutableVec outVec);
+ method public final androidx.ink.geometry.ImmutableVec computeUnitVec();
+ method public final androidx.ink.geometry.MutableVec computeUnitVec(androidx.ink.geometry.MutableVec outVec);
+ method public static final float determinant(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public static final void divide(androidx.ink.geometry.Vec lhs, float rhs, androidx.ink.geometry.MutableVec output);
+ method public static final float dotProduct(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public abstract float getX();
+ method public abstract float getY();
+ method public final boolean isAlmostEqual(androidx.ink.geometry.Vec other);
+ method public final boolean isAlmostEqual(androidx.ink.geometry.Vec other, optional @FloatRange(from=0.0) float tolerance);
+ method public final boolean isParallelTo(androidx.ink.geometry.Vec other, @FloatRange(from=0.0) @androidx.ink.geometry.AngleRadiansFloat float angleTolerance);
+ method public final boolean isPerpendicularTo(androidx.ink.geometry.Vec other, @FloatRange(from=0.0) @androidx.ink.geometry.AngleRadiansFloat float angleTolerance);
+ method public static final void multiply(androidx.ink.geometry.Vec lhs, float rhs, androidx.ink.geometry.MutableVec output);
+ method public static final void multiply(float lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ method @FloatRange(from=-3.141592653589793, to=java.lang.Math.PI, fromInclusive=false) @androidx.ink.geometry.AngleRadiansFloat public static final float signedAngleBetween(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public static final void subtract(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ property public abstract float x;
+ property public abstract float y;
+ field public static final androidx.ink.geometry.Vec.Companion Companion;
+ field public static final androidx.ink.geometry.ImmutableVec ORIGIN;
+ }
+
+ public static final class Vec.Companion {
+ method @FloatRange(from=0.0, to=java.lang.Math.PI) @androidx.ink.geometry.AngleRadiansFloat public float absoluteAngleBetween(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public void add(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ method public float determinant(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public void divide(androidx.ink.geometry.Vec lhs, float rhs, androidx.ink.geometry.MutableVec output);
+ method public float dotProduct(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public void multiply(androidx.ink.geometry.Vec lhs, float rhs, androidx.ink.geometry.MutableVec output);
+ method public void multiply(float lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ method @FloatRange(from=-3.141592653589793, to=java.lang.Math.PI, fromInclusive=false) @androidx.ink.geometry.AngleRadiansFloat public float signedAngleBetween(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs);
+ method public void subtract(androidx.ink.geometry.Vec lhs, androidx.ink.geometry.Vec rhs, androidx.ink.geometry.MutableVec output);
+ }
+
}
diff --git a/ink/ink-geometry/src/androidInstrumentedTest/kotlin/androidx/ink/geometry/EnvelopeExtensionsTest.kt b/ink/ink-geometry/src/androidInstrumentedTest/kotlin/androidx/ink/geometry/EnvelopeExtensionsTest.kt
index 44862ce..f98e981 100644
--- a/ink/ink-geometry/src/androidInstrumentedTest/kotlin/androidx/ink/geometry/EnvelopeExtensionsTest.kt
+++ b/ink/ink-geometry/src/androidInstrumentedTest/kotlin/androidx/ink/geometry/EnvelopeExtensionsTest.kt
@@ -40,7 +40,7 @@
fun getBoundsRectF_whenHasBounds_returnsTrueAndOverwritesOutParameter() {
val envelope =
BoxAccumulator()
- .add(MutableBox().fillFromTwoPoints(ImmutablePoint(1f, 2f), ImmutablePoint(3f, 4f)))
+ .add(MutableBox().populateFromTwoPoints(ImmutableVec(1f, 2f), ImmutableVec(3f, 4f)))
val outRect = RectF(5F, 6F, 7F, 8F)
assertThat(envelope.getBounds(outRect)).isTrue()
diff --git a/ink/ink-geometry/src/androidMain/kotlin/androidx/ink/geometry/AndroidGraphicsConversionExtensions.android.kt b/ink/ink-geometry/src/androidMain/kotlin/androidx/ink/geometry/AndroidGraphicsConversionExtensions.android.kt
index ae923f3..6efc190 100644
--- a/ink/ink-geometry/src/androidMain/kotlin/androidx/ink/geometry/AndroidGraphicsConversionExtensions.android.kt
+++ b/ink/ink-geometry/src/androidMain/kotlin/androidx/ink/geometry/AndroidGraphicsConversionExtensions.android.kt
@@ -29,12 +29,12 @@
/** Writes the values from this [AffineTransform] to [matrixOut]. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
public fun AffineTransform.populateMatrix(matrixOut: Matrix) {
- matrixValuesScratchArray[Matrix.MSCALE_X] = a
- matrixValuesScratchArray[Matrix.MSKEW_X] = b
- matrixValuesScratchArray[Matrix.MTRANS_X] = c
- matrixValuesScratchArray[Matrix.MSKEW_Y] = d
- matrixValuesScratchArray[Matrix.MSCALE_Y] = e
- matrixValuesScratchArray[Matrix.MTRANS_Y] = f
+ matrixValuesScratchArray[Matrix.MSCALE_X] = m00
+ matrixValuesScratchArray[Matrix.MSKEW_X] = m10
+ matrixValuesScratchArray[Matrix.MTRANS_X] = m20
+ matrixValuesScratchArray[Matrix.MSKEW_Y] = m01
+ matrixValuesScratchArray[Matrix.MSCALE_Y] = m11
+ matrixValuesScratchArray[Matrix.MTRANS_Y] = m21
matrixValuesScratchArray[Matrix.MPERSP_0] = 0f
matrixValuesScratchArray[Matrix.MPERSP_1] = 0f
matrixValuesScratchArray[Matrix.MPERSP_2] = 1f
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/AffineTransform.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/AffineTransform.kt
index d190a0a..611db89 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/AffineTransform.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/AffineTransform.kt
@@ -17,22 +17,23 @@
package androidx.ink.geometry
import androidx.annotation.RestrictTo
+import androidx.annotation.Size
import kotlin.jvm.JvmField
/**
* An affine transformation in the plane. The transformation can be thought of as a 3x3 matrix:
* ```
- * ⎡a b c⎤
- * ⎢d e f⎥
- * ⎣0 0 1⎦
+ * ⎡m00 m10 m20⎤
+ * ⎢m01 m11 m21⎥
+ * ⎣ 0 0 1 ⎦
* ```
*
* Applying the transformation can be thought of as a matrix multiplication, with the
* to-be-transformed point represented as a column vector with an extra 1:
* ```
- * ⎡a b c⎤ ⎡x⎤ ⎡a*x + b*y + c⎤
- * ⎢d e f⎥ * ⎢y⎥ = ⎢d*x + e*y + f⎥
- * ⎣0 0 1⎦ ⎣1⎦ ⎣ 1 ⎦
+ * ⎡m00 m10 m20⎤ ⎡x⎤ ⎡m00*x + m10*y + m20⎤
+ * ⎢m01 m11 m21⎥ * ⎢y⎥ = ⎢m01*x + m11*y + m21⎥
+ * ⎣ 0 0 1 ⎦ ⎣1⎦ ⎣ 1 ⎦
* ```
*
* Transformations are composed via multiplication. Multiplication is not commutative (i.e. A*B !=
@@ -43,109 +44,90 @@
* val translate = ImmutableAffineTransform.translate(Vec(10, 0))
* ```
*
- * then the `rotate * translate` first translates 10 units in the positive x-direction, then rotates
- * 90° about the origin.
+ * then `rotate * translate` first translates 10 units in the positive x-direction, then rotates 45°
+ * about the origin.
*
- * This class follows AndroidX guidelines ({@link http://go/androidx-api-guidelines#kotlin-data}) to
- * avoid Kotlin data classes.
- *
- * See [MutableAffineTransform] and [ImmutableAffineTransform] for implementations.
+ * [ImmutableAffineTransform] and [MutableAffineTransform] are the two concrete implementations of
+ * this.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public interface AffineTransform {
- public val a: Float
- public val b: Float
- public val c: Float
- public val d: Float
- public val e: Float
- public val f: Float
+public abstract class AffineTransform internal constructor() {
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ public abstract val m00: Float
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ public abstract val m10: Float
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ public abstract val m20: Float
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ public abstract val m01: Float
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ public abstract val m11: Float
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ public abstract val m21: Float
/**
* Returns an immutable copy of this object. This will return itself if called on an immutable
* instance.
*/
- public fun asImmutable(): ImmutableAffineTransform {
- return ImmutableAffineTransform(
- a = this.a,
- b = this.b,
- c = this.c,
- d = this.d,
- e = this.e,
- f = this.f,
- )
- }
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public abstract fun asImmutable(): ImmutableAffineTransform
/**
- * Populates [output] with the inverse of the [AffineTransform]. The same MutableAffineTransform
- * can be used as the output to avoid additional allocations.
+ * Populates [outAffineTransform] with the inverse of this [AffineTransform]. The same
+ * [MutableAffineTransform] instance can be used as the output to avoid additional allocations.
+ * Returns [outAffineTransform].
*/
- public fun populateInverse(output: MutableAffineTransform) {
- val determinant = a * e - b * d
+ public fun computeInverse(outAffineTransform: MutableAffineTransform): MutableAffineTransform {
+ val determinant = m00 * m11 - m10 * m01
require(determinant != 0F) {
"The inverse of the AffineTransform cannot be found because the determinant is 0."
}
- val newA = e / determinant
- val newB = -b / determinant
- val newC = (b * f - c * e) / determinant
- val newD = -d / determinant
- val newE = a / determinant
- val newF = (c * d - a * f) / determinant
- output.a = newA
- output.b = newB
- output.c = newC
- output.d = newD
- output.e = newE
- output.f = newF
+ val newM00 = m11 / determinant
+ val newM10 = -m10 / determinant
+ val newM20 = (m10 * m21 - m20 * m11) / determinant
+ val newM01 = -m01 / determinant
+ val newM11 = m00 / determinant
+ val newM21 = (m20 * m01 - m00 * m21) / determinant
+ outAffineTransform.setValues(newM00, newM10, newM20, newM01, newM11, newM21)
+ return outAffineTransform
}
- private fun transformX(x: Float, y: Float): Float = a * x + b * y + c
+ private fun applyTransformX(x: Float, y: Float): Float = m00 * x + m10 * y + m20
- private fun transformY(x: Float, y: Float): Float = d * x + e * y + f
+ private fun applyTransformY(x: Float, y: Float): Float = m01 * x + m11 * y + m21
/**
* Apply the [AffineTransform] to the [Vec] and store the result in the [MutableVec]. The same
* [MutableVec] can be used as both the input and output to avoid additional allocations.
+ * Returns [outVec].
*/
- public fun applyTransform(vec: Vec, output: MutableVec) {
- val newX = transformX(vec.x, vec.y)
- output.y = transformY(vec.x, vec.y)
- output.x = newX
+ public fun applyTransform(vec: Vec, outVec: MutableVec): MutableVec {
+ val newX = applyTransformX(vec.x, vec.y)
+ outVec.y = applyTransformY(vec.x, vec.y)
+ outVec.x = newX
+ return outVec
}
/**
* Apply the [AffineTransform] to the [Segment] and store the result in the [MutableSegment].
* The same [MutableSegment] can be used as both the input and output to avoid additional
- * allocations.
+ * allocations. Returns [outSegment].
*/
- public fun applyTransform(segment: Segment, output: MutableSegment) {
- output.start(
- transformX(segment.start.x, segment.start.y),
- transformY(segment.start.x, segment.start.y),
- )
- output.end(
- transformX(segment.end.x, segment.end.y),
- transformY(segment.end.x, segment.end.y)
- )
+ public fun applyTransform(segment: Segment, outSegment: MutableSegment): MutableSegment {
+ applyTransform(segment.start, outSegment.start)
+ applyTransform(segment.end, outSegment.end)
+ return outSegment
}
/**
* Apply the [AffineTransform] to the [Triangle] and store the result in the [MutableTriangle].
* The same [MutableTriangle] can be used as both the input and output to avoid additional
- * allocations.
+ * allocations. Returns [outTriangle].
*/
- public fun applyTransform(triangle: Triangle, output: MutableTriangle) {
- output.p0(
- transformX(triangle.p0.x, triangle.p0.y),
- transformY(triangle.p0.x, triangle.p0.y)
- )
- output.p1(
- transformX(triangle.p1.x, triangle.p1.y),
- transformY(triangle.p1.x, triangle.p1.y)
- )
- output.p2(
- transformX(triangle.p2.x, triangle.p2.y),
- transformY(triangle.p2.x, triangle.p2.y)
- )
+ public fun applyTransform(triangle: Triangle, outTriangle: MutableTriangle): MutableTriangle {
+ applyTransform(triangle.p0, outTriangle.p0)
+ applyTransform(triangle.p1, outTriangle.p1)
+ applyTransform(triangle.p2, outTriangle.p2)
+ return outTriangle
}
/**
@@ -153,22 +135,26 @@
* This is the only Apply function where the input cannot also be the output, as applying an
* Affine Transform to a Box makes a Parallelogram.
*/
- public fun applyTransform(box: Box, outputParallelogram: MutableParallelogram) {
+ public fun applyTransform(
+ box: Box,
+ outParallelogram: MutableParallelogram,
+ ): MutableParallelogram {
AffineTransformHelper.nativeApplyParallelogram(
- affineTransformA = a,
- affineTransformB = b,
- affineTransformC = c,
- affineTransformD = d,
- affineTransformE = e,
- affineTransformF = f,
+ affineTransformA = m00,
+ affineTransformB = m10,
+ affineTransformC = m20,
+ affineTransformD = m01,
+ affineTransformE = m11,
+ affineTransformF = m21,
parallelogramCenterX = (box.xMin + box.xMax) / 2,
parallelogramCenterY = (box.yMin + box.yMax) / 2,
parallelogramWidth = box.width,
parallelogramHeight = box.height,
parallelogramRotation = 0f,
parallelogramShearFactor = 0f,
- out = outputParallelogram,
+ out = outParallelogram,
)
+ return outParallelogram
}
/**
@@ -178,23 +164,52 @@
*/
public fun applyTransform(
parallelogram: Parallelogram,
- outputParallelogram: MutableParallelogram,
- ) {
+ outParallelogram: MutableParallelogram,
+ ): MutableParallelogram {
AffineTransformHelper.nativeApplyParallelogram(
- affineTransformA = a,
- affineTransformB = b,
- affineTransformC = c,
- affineTransformD = d,
- affineTransformE = e,
- affineTransformF = f,
+ affineTransformA = m00,
+ affineTransformB = m10,
+ affineTransformC = m20,
+ affineTransformD = m01,
+ affineTransformE = m11,
+ affineTransformF = m21,
parallelogramCenterX = parallelogram.center.x,
parallelogramCenterY = parallelogram.center.y,
parallelogramWidth = parallelogram.width,
parallelogramHeight = parallelogram.height,
parallelogramRotation = parallelogram.rotation,
parallelogramShearFactor = parallelogram.shearFactor,
- out = outputParallelogram,
+ out = outParallelogram,
)
+ return outParallelogram
+ }
+
+ /**
+ * Populates the first 6 elements of [outArray] with the values of this transform, starting with
+ * the top left corner of the matrix and proceeding in row-major order.
+ *
+ * In performance-sensitive code, prefer to pass in an array that has already been allocated and
+ * is being reused, rather than relying on the default behavior of allocating a new instance for
+ * each call.
+ *
+ * Prefer to apply this transform to an object, such as with [applyTransform], rather than
+ * accessing the actual numeric values of this transform. This function is useful for when the
+ * values are needed in bulk but not to apply a transform, for example for serialization.
+ *
+ * To set these values on a transform in the same order that they are retrieved here, use the
+ * [ImmutableAffineTransform] constructor or use [MutableAffineTransform.setValues].
+ */
+ @JvmOverloads
+ @Size(min = 6)
+ @Suppress("ArrayReturn") // Returning the input value for chaining.
+ public fun getValues(@Size(min = 6) outArray: FloatArray = FloatArray(6)): FloatArray {
+ outArray[0] = m00
+ outArray[1] = m10
+ outArray[2] = m20
+ outArray[3] = m01
+ outArray[4] = m11
+ outArray[5] = m21
+ return outArray
}
public companion object {
@@ -204,29 +219,29 @@
*/
@JvmField
public val IDENTITY: ImmutableAffineTransform =
- ImmutableAffineTransform(a = 1f, b = 0f, c = 0f, d = 0f, e = 1f, f = 0f)
+ ImmutableAffineTransform(1f, 0f, 0f, 0f, 1f, 0f)
/**
* Returns true if [first] and [second] have the same values for all properties of
* [AffineTransform].
*/
internal fun areEquivalent(first: AffineTransform, second: AffineTransform): Boolean =
- first.a == second.a &&
- first.b == second.b &&
- first.c == second.c &&
- first.d == second.d &&
- first.e == second.e &&
- first.f == second.f
+ first.m00 == second.m00 &&
+ first.m10 == second.m10 &&
+ first.m20 == second.m20 &&
+ first.m01 == second.m01 &&
+ first.m11 == second.m11 &&
+ first.m21 == second.m21
/** Returns a hash code for [affineTransform] using its [AffineTransform] properties. */
internal fun hash(affineTransform: AffineTransform): Int =
affineTransform.run {
- var result = a.hashCode()
- result = 31 * result + b.hashCode()
- result = 31 * result + c.hashCode()
- result = 31 * result + d.hashCode()
- result = 31 * result + e.hashCode()
- result = 31 * result + f.hashCode()
+ var result = m00.hashCode()
+ result = 31 * result + m10.hashCode()
+ result = 31 * result + m20.hashCode()
+ result = 31 * result + m01.hashCode()
+ result = 31 * result + m11.hashCode()
+ result = 31 * result + m21.hashCode()
return result
}
@@ -235,6 +250,8 @@
* properties.
*/
internal fun string(affineTransform: AffineTransform): String =
- affineTransform.run { "AffineTransform(a=$a, b=$b, c=$c, d=$d, e=$e, f=$f)" }
+ affineTransform.run {
+ "AffineTransform(m00=$m00, m10=$m10, m20=$m20, m01=$m01, m11=$m11, m21=$m21)"
+ }
}
}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Box.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Box.kt
index 314a735..26e596b 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Box.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Box.kt
@@ -17,7 +17,6 @@
package androidx.ink.geometry
import androidx.annotation.FloatRange
-import androidx.annotation.RestrictTo
import kotlin.math.abs
/**
@@ -26,19 +25,18 @@
*
* The [Box] interface is the read-only view of the underlying data which may or may not be mutable.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public interface Box {
+public abstract class Box internal constructor() {
/** The lower bound in the `X` direction. */
- public val xMin: Float
+ public abstract val xMin: Float
/** The lower bound in the `Y` direction. */
- public val yMin: Float
+ public abstract val yMin: Float
/** The upper bound in the `X` direction. */
- public val xMax: Float
+ public abstract val xMax: Float
/** The upper bound in the `Y` direction. */
- public val yMax: Float
+ public abstract val yMax: Float
/** The width of the rectangle. This can never be negative. */
public val width: Float
@@ -48,34 +46,37 @@
public val height: Float
@FloatRange(from = 0.0) get() = yMax - yMin
- /** Populates [out] with the center of the [Box]. */
- public fun center(out: MutablePoint)
+ /** Populates [outVec] with the center of the [Box], and returns [outVec]. */
+ public fun computeCenter(outVec: MutableVec): MutableVec {
+ BoxHelper.nativeCenter(xMin, yMin, xMax, yMax, outVec)
+ return outVec
+ }
/**
- * Populates the 4 [output] points with the corners of the [Box]. The order of the corners is:
+ * Populates the 4 output points with the corners of the [Box]. The order of the corners is:
* (x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)
*/
- public fun corners(
- outputXMinYMin: MutablePoint,
- outputXMaxYMin: MutablePoint,
- outputXMaxYMax: MutablePoint,
- outputXMinYMax: MutablePoint,
+ public fun computeCorners(
+ outVecXMinYMin: MutableVec,
+ outVecXMaxYMin: MutableVec,
+ outVecXMaxYMax: MutableVec,
+ outVecXMinYMax: MutableVec,
) {
- outputXMinYMin.x = xMin
- outputXMinYMin.y = yMin
- outputXMaxYMin.x = xMax
- outputXMaxYMin.y = yMin
- outputXMaxYMax.x = xMax
- outputXMaxYMax.y = yMax
- outputXMinYMax.x = xMin
- outputXMinYMax.y = yMax
+ outVecXMinYMin.x = xMin
+ outVecXMinYMin.y = yMin
+ outVecXMaxYMin.x = xMax
+ outVecXMaxYMin.y = yMin
+ outVecXMaxYMax.x = xMax
+ outVecXMaxYMax.y = yMax
+ outVecXMinYMax.x = xMin
+ outVecXMinYMax.y = yMax
}
/**
* Returns whether the given point is contained within the Box. Points that lie exactly on the
* Box's boundary are considered to be contained.
*/
- public operator fun contains(point: Point): Boolean =
+ public operator fun contains(point: Vec): Boolean =
BoxHelper.nativeContainsPoint(xMin, yMin, xMax, yMax, point.x, point.y)
/**
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxAccumulator.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxAccumulator.kt
index acf7fb6..f14b398 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxAccumulator.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxAccumulator.kt
@@ -19,8 +19,6 @@
import androidx.annotation.FloatRange
import androidx.annotation.RestrictTo
import androidx.ink.nativeloader.NativeLoader
-import kotlin.Deprecated
-import kotlin.jvm.JvmSynthetic
/**
* A helper class for accumulating the minimum bounding boxes of zero or more geometry objects. In
@@ -28,7 +26,6 @@
*/
// TODO: b/355248266 - @UsedByNative("envelope_jni_helper.cc") must go in Proguard config file
// instead.
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
public class BoxAccumulator {
/**
* The bounds, which are valid only if [hasBounds] is `true`. When [hasBounds] is `false`, this
@@ -59,9 +56,9 @@
) : this(
true,
MutableBox()
- .fillFromTwoPoints(
- ImmutablePoint(box.xMin, box.yMin),
- ImmutablePoint(box.xMax, box.yMax)
+ .populateFromTwoPoints(
+ ImmutableVec(box.xMin, box.yMin),
+ ImmutableVec(box.xMax, box.yMax)
),
)
@@ -75,16 +72,23 @@
*/
public fun isEmpty(): Boolean = !hasBounds
- /** Populates this [BoxAccumulator] with the same values contained in [input]. */
+ /**
+ * Populates this [BoxAccumulator] with the same values contained in [input].
+ *
+ * @return `this`
+ */
public fun populateFrom(input: BoxAccumulator): BoxAccumulator {
reset().add(input)
return this
}
- /** Reset this object to have no bounds. Returns the same instance to chain function calls. */
+ /**
+ * Reset this object to have no bounds.
+ *
+ * @return `this`
+ */
// TODO: b/355248266 - @UsedByNative("envelope_jni_helper.cc") must go in Proguard config file
// instead.
-
public fun reset(): BoxAccumulator {
hasBounds = false
_bounds.setXBounds(Float.NaN, Float.NaN).setYBounds(Float.NaN, Float.NaN)
@@ -94,6 +98,8 @@
/**
* Expands the accumulated bounding box (if necessary) such that it also contains [other]. If
* [other] is null, this is a no-op.
+ *
+ * @return `this`
*/
public fun add(other: BoxAccumulator?): BoxAccumulator {
BoxAccumulatorNative.nativeAddOptionalBox(
@@ -115,6 +121,8 @@
/**
* Expands the accumulated bounding box (if necessary) such that it also contains [point]. If
* [point] is null, this is a no-op.
+ *
+ * @return `this`
*/
public fun add(point: Vec): BoxAccumulator {
BoxAccumulatorNative.nativeAddPoint(
@@ -133,6 +141,8 @@
/**
* Expands the accumulated bounding box (if necessary) such that it also contains [segment]. If
* [segment] is null, this is a no-op.
+ *
+ * @return `this`
*/
public fun add(segment: Segment): BoxAccumulator {
BoxAccumulatorNative.nativeAddSegment(
@@ -153,6 +163,8 @@
/**
* Expands the accumulated bounding box (if necessary) such that it also contains [triangle]. If
* [triangle] is null, this is a no-op.
+ *
+ * @return `this`
*/
public fun add(triangle: Triangle): BoxAccumulator {
BoxAccumulatorNative.nativeAddTriangle(
@@ -175,6 +187,8 @@
/**
* Expands the accumulated bounding box (if necessary) such that it also contains [box]. If
* [box] is null, this is a no-op.
+ *
+ * @return `this`
*/
public fun add(box: Box?): BoxAccumulator {
BoxAccumulatorNative.nativeAddOptionalBox(
@@ -196,6 +210,8 @@
/**
* Expands the accumulated bounding box (if necessary) such that it also contains
* [parallelogram]. If [parallelogram] is null, this is a no-op.
+ *
+ * @return `this`
*/
public fun add(parallelogram: Parallelogram): BoxAccumulator {
BoxAccumulatorNative.nativeAddParallelogram(
@@ -218,8 +234,11 @@
/**
* Expands the accumulated bounding box (if necessary) such that it also contains [mesh]. If
* [mesh] is null or empty, this is a no-op.
+ *
+ * @return `this`
*/
- public fun add(mesh: ModeledShape): BoxAccumulator = this.add(mesh.bounds)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public fun add(mesh: PartitionedMesh): BoxAccumulator = this.add(mesh.bounds)
/**
* Compares this [BoxAccumulator] with [other], and returns true if either: Both this and
@@ -235,13 +254,13 @@
/**
* Overwrite the entries of this object with new values. This is useful for recycling an
- * instance. Returns the same instance to chain function calls.
+ * instance.
+ *
+ * @return `this`
*/
// TODO: b/355248266 - @UsedByNative("envelope_jni_helper.cc") must go in Proguard config file
// instead.
-
- @JvmSynthetic
- @Deprecated("Prefer to use methods [reset] and [add]")
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
public fun overwriteFrom(x1: Float, y1: Float, x2: Float, y2: Float): BoxAccumulator {
hasBounds = true
_bounds.setXBounds(x1, x2).setYBounds(y1, y2)
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxHelper.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxHelper.kt
index 087e544..68f902b 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxHelper.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxHelper.kt
@@ -31,7 +31,7 @@
rectYMin: Float,
rectXMax: Float,
rectYMax: Float,
- out: MutablePoint,
+ out: MutableVec,
)
// TODO: b/355248266 - @Keep must go in Proguard config file instead.
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableAffineTransform.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableAffineTransform.kt
index aea127e..1b417cb 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableAffineTransform.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableAffineTransform.kt
@@ -17,21 +17,22 @@
package androidx.ink.geometry
import androidx.annotation.RestrictTo
+import androidx.annotation.Size
/**
* An affine transformation in the plane. The transformation can be thought of as a 3x3 matrix:
* ```
- * ⎡a b c⎤
- * ⎢d e f⎥
- * ⎣0 0 1⎦
+ * ⎡m00 m10 m20⎤
+ * ⎢m01 m11 m21⎥
+ * ⎣ 0 0 1 ⎦
* ```
*
* Applying the transformation can be thought of as a matrix multiplication, with the
* to-be-transformed point represented as a column vector with an extra 1:
* ```
- * ⎡a b c⎤ ⎡x⎤ ⎡a*x + b*y + c⎤
- * ⎢d e f⎥ * ⎢y⎥ = ⎢d*x + e*y + f⎥
- * ⎣0 0 1⎦ ⎣1⎦ ⎣ 1 ⎦
+ * ⎡m00 m10 m20⎤ ⎡x⎤ ⎡m00*x + m10*y + m20⎤
+ * ⎢m01 m11 m21⎥ * ⎢y⎥ = ⎢m01*x + m11*y + m21⎥
+ * ⎣ 0 0 1 ⎦ ⎣1⎦ ⎣ 1 ⎦
* ```
*
* Transformations are composed via multiplication. Multiplication is not commutative (i.e. A*B !=
@@ -42,25 +43,45 @@
* val translate = ImmutableAffineTransform.translate(Vec(10, 0))
* ```
*
- * then the `rotate * translate` first translates 10 units in the positive x-direction, then rotates
- * 90° about the origin.
- *
- * This class follows AndroidX guidelines ({@link http://go/androidx-api-guidelines#kotlin-data}) to
- * avoid Kotlin data classes.
+ * then `rotate * translate` first translates 10 units in the positive x-direction, then rotates 45°
+ * about the origin.
*
* See [MutableAffineTransform] for mutable alternative to this class.
+ *
+ * @constructor Constructs this transform with 6 float values, starting with the top left corner of
+ * the matrix and proceeding in row-major order. Prefer to create this object with functions that
+ * apply specific transform operations, such as [scale] or [translate], rather than directly
+ * passing in the actual numeric values of this transform. This constructor is useful for when the
+ * values are needed to be provided all at once, for example for serialization. To access these
+ * values in the same order as they are passed in here, use [AffineTransform.getValues]. To
+ * construct this object using an array as input, there is another public constructor for that.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public class ImmutableAffineTransform(
- override val a: Float,
- override val b: Float,
- override val c: Float,
- override val d: Float,
- override val e: Float,
- override val f: Float,
-) : AffineTransform {
+public class ImmutableAffineTransform
+public constructor(
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override val m00: Float,
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override val m10: Float,
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override val m20: Float,
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override val m01: Float,
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override val m11: Float,
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override val m21: Float,
+) : AffineTransform() {
- override public fun asImmutable(): ImmutableAffineTransform = this
+ /**
+ * Like the primary constructor, but accepts a [FloatArray] instead of individual [Float]
+ * values.
+ */
+ public constructor(
+ @Size(min = 6) values: FloatArray
+ ) : this(values[0], values[1], values[2], values[3], values[4], values[5])
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public override fun asImmutable(): ImmutableAffineTransform = this
/**
* Component-wise equality operator for [ImmutableAffineTransform].
@@ -70,18 +91,18 @@
* [equals] in some cases.
*/
override fun equals(other: Any?): Boolean =
- other === this || (other is AffineTransform && AffineTransform.areEquivalent(this, other))
+ other === this || (other is AffineTransform && areEquivalent(this, other))
// NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode
- override fun hashCode(): Int = AffineTransform.hash(this)
+ override fun hashCode(): Int = hash(this)
- override fun toString(): String = "Immutable${AffineTransform.hash(this)}"
+ override fun toString(): String = "Immutable${string(this)}"
public companion object {
/** Returns a transformation that translates by the given [offset] vector. */
@JvmStatic
public fun translate(offset: Vec): ImmutableAffineTransform =
- ImmutableAffineTransform(a = 1f, b = 0f, c = offset.x, d = 0f, e = 1f, f = offset.y)
+ ImmutableAffineTransform(1f, 0f, offset.x, 0f, 1f, offset.y)
/**
* Returns a transformation that scales in both the x- and y-direction by the given pair of
@@ -89,14 +110,7 @@
*/
@JvmStatic
public fun scale(xScaleFactor: Float, yScaleFactor: Float): ImmutableAffineTransform =
- ImmutableAffineTransform(
- a = xScaleFactor,
- b = 0f,
- c = 0f,
- d = 0f,
- e = yScaleFactor,
- f = 0f
- )
+ ImmutableAffineTransform(xScaleFactor, 0f, 0f, 0f, yScaleFactor, 0f)
/**
* Returns a transformation that scales in both the x- and y-direction by the given
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableBox.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableBox.kt
index 163c613..916ce00 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableBox.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableBox.kt
@@ -17,7 +17,6 @@
package androidx.ink.geometry
import androidx.annotation.FloatRange
-import androidx.annotation.RestrictTo
import kotlin.math.max
import kotlin.math.min
@@ -28,8 +27,7 @@
* (e.g. the positive `Y` axis being "down"), because it is intended to be used with any coordinate
* system rather than just Android screen/View space.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public class ImmutableBox private constructor(x1: Float, y1: Float, x2: Float, y2: Float) : Box {
+public class ImmutableBox private constructor(x1: Float, y1: Float, x2: Float, y2: Float) : Box() {
/** The lower bound in the `X` direction. */
override val xMin: Float = min(x1, x2)
@@ -43,35 +41,6 @@
/** The upper bound in the `Y` direction. */
override val yMax: Float = max(y1, y2)
- public fun fillMutable(output: MutableBox) {
- output.setXBounds(xMin, xMax).setYBounds(yMin, yMax)
- }
-
- public fun newMutable(): MutableBox {
- return MutableBox().setXBounds(xMin, xMax).setYBounds(yMin, yMax)
- }
-
- /** Populates [out] with the center of the [ImmutableBox]. */
- override fun center(out: MutablePoint): Unit =
- BoxHelper.nativeCenter(xMin, yMin, xMax, yMax, out)
-
- /**
- * Return a copy of this object with modified values as provided, where [x1] and [y1] default to
- * minimum values and [x2] and [y2] default to the maximum values, respectively.
- */
- @JvmSynthetic
- public fun copy(
- x1: Float = this.xMin,
- y1: Float = this.yMin,
- x2: Float = this.xMax,
- y2: Float = this.yMax,
- ): ImmutableBox =
- if (this.xMin == x1 && this.yMin == y1 && this.xMax == x2 && this.yMax == y2) {
- this
- } else {
- ImmutableBox(x1, y1, x2, y2)
- }
-
override fun equals(other: Any?): Boolean =
other === this || (other is Box && Box.areEquivalent(this, other))
@@ -84,7 +53,7 @@
/** Constructs an [ImmutableBox] with a given [center], [width], and [height]. */
@JvmStatic
public fun fromCenterAndDimensions(
- center: Point,
+ center: Vec,
@FloatRange(from = 0.0) width: Float,
@FloatRange(from = 0.0) height: Float,
): ImmutableBox {
@@ -99,7 +68,7 @@
/** Constructs the smallest [ImmutableBox] containing the two given points. */
@JvmStatic
- public fun fromTwoPoints(point1: Point, point2: Point): ImmutableBox {
+ public fun fromTwoPoints(point1: Vec, point2: Vec): ImmutableBox {
return ImmutableBox(point1.x, point1.y, point2.x, point2.y)
}
}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableParallelogram.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableParallelogram.kt
index 95ca1f7..750093c 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableParallelogram.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableParallelogram.kt
@@ -17,29 +17,27 @@
package androidx.ink.geometry
import androidx.annotation.FloatRange
-import androidx.annotation.RestrictTo
/**
* Immutable parallelogram (i.e. a quadrilateral with parallel sides), defined by its [center],
* [width], [height], [rotation], and [shearFactor].
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
public class ImmutableParallelogram
private constructor(
- override val center: ImmutablePoint,
+ override val center: ImmutableVec,
override val width: Float,
override val height: Float,
@AngleRadiansFloat override val rotation: Float,
override val shearFactor: Float,
-) : Parallelogram {
+) : Parallelogram() {
override fun equals(other: Any?): Boolean =
- other === this || (other is Parallelogram && Parallelogram.areEquivalent(this, other))
+ other === this || (other is Parallelogram && areEquivalent(this, other))
// NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode
- override fun hashCode(): Int = Parallelogram.hash(this)
+ override fun hashCode(): Int = hash(this)
- override fun toString(): String = "Immutable${Parallelogram.string(this)}"
+ override fun toString(): String = "Immutable${string(this)}"
public companion object {
@@ -50,14 +48,11 @@
*/
@JvmStatic
public fun fromCenterAndDimensions(
- center: ImmutablePoint,
+ center: ImmutableVec,
@FloatRange(from = 0.0) width: Float,
height: Float,
): ImmutableParallelogram =
- Parallelogram.normalizeAndRun(width, height, rotation = Angle.ZERO) {
- w: Float,
- h: Float,
- r: Float ->
+ normalizeAndRun(width, height, rotation = Angle.ZERO) { w: Float, h: Float, r: Float ->
ImmutableParallelogram(center, w, h, r, shearFactor = 0f)
}
@@ -69,12 +64,12 @@
*/
@JvmStatic
public fun fromCenterDimensionsAndRotation(
- center: ImmutablePoint,
+ center: ImmutableVec,
@FloatRange(from = 0.0) width: Float,
height: Float,
@AngleRadiansFloat rotation: Float,
): ImmutableParallelogram =
- Parallelogram.normalizeAndRun(width, height, rotation) { w: Float, h: Float, r: Float ->
+ normalizeAndRun(width, height, rotation) { w: Float, h: Float, r: Float ->
ImmutableParallelogram(center, w, h, r, shearFactor = 0f)
}
@@ -85,13 +80,13 @@
*/
@JvmStatic
public fun fromCenterDimensionsRotationAndShear(
- center: ImmutablePoint,
+ center: ImmutableVec,
@FloatRange(from = 0.0) width: Float,
height: Float,
@AngleRadiansFloat rotation: Float,
shearFactor: Float,
): ImmutableParallelogram =
- Parallelogram.normalizeAndRun(width, height, rotation) { w: Float, h: Float, r: Float ->
+ normalizeAndRun(width, height, rotation) { w: Float, h: Float, r: Float ->
ImmutableParallelogram(center, w, h, r, shearFactor)
}
}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutablePoint.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutablePoint.kt
deleted file mode 100644
index 44eb30c..0000000
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutablePoint.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2024 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.ink.geometry
-
-import androidx.annotation.RestrictTo
-import kotlin.jvm.JvmSynthetic
-
-/** Represents a location in 2-dimensional space. See [MutablePoint] for a mutable alternative. */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public class ImmutablePoint(override val x: Float, override val y: Float) : Point {
- /** Fills [output] with the x and y coordinates of this [ImmutablePoint] */
- public fun fillMutable(output: MutablePoint) {
- output.x = this.x
- output.y = this.y
- }
-
- /** Returns a [MutablePoint] containing the same x and y coordinates as this [ImmutablePoint] */
- public fun newMutable(): MutablePoint {
- return MutablePoint(x, y)
- }
-
- /** Return a copy of this object with modified x and y as provided. */
- @JvmSynthetic
- public fun copy(x: Float = this.x, y: Float = this.y): ImmutablePoint =
- if (x == this.x && y == this.y) this else ImmutablePoint(x, y)
-
- override fun equals(other: Any?): Boolean =
- other === this || (other is Point && Point.areEquivalent(this, other))
-
- // NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode
- override fun hashCode(): Int = Point.hash(this)
-
- override fun toString(): String = "Immutable${Point.string(this)}"
-}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableSegment.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableSegment.kt
index 4e965ce..a7e20db 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableSegment.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableSegment.kt
@@ -22,54 +22,18 @@
* Represents a directed line segment between two points. See [MutableSegment] for mutable
* alternative.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public class ImmutableSegment(start: Vec, end: Vec) : Segment {
+public class ImmutableSegment(start: Vec, end: Vec) : Segment() {
- @Suppress("Immutable") override val start: Vec = start.asImmutable
- @Suppress("Immutable") override val end: Vec = end.asImmutable
+ @Suppress("Immutable") override val start: Vec = start.asImmutable()
+ @Suppress("Immutable") override val end: Vec = end.asImmutable()
- /**
- * Caches the result of [vec] if it is called. This format is used to avoid unnecessary
- * allocations on construction, and avoid extra allocations if [vec] is called multiple times.
- * Although the Immutable lint is being suppressed, this object is still immutable as its
- * visible data cannot be modified.
- */
- @Suppress("Immutable") private var _vec: ImmutableVec? = null
-
- override val vec: ImmutableVec
- get() = _vec ?: ImmutableVec(end.x - start.x, end.y - start.y).also { _vec = it }
-
- override fun asImmutable(): ImmutableSegment = this
-
- @JvmSynthetic
- override fun asImmutable(start: Vec, end: Vec): ImmutableSegment {
- if (this.start === start && this.end === end) {
- return this
- }
-
- return ImmutableSegment(start, end)
- }
-
- /**
- * Caches the result of [midpoint] if it is called. This format is used to avoid unnecessary
- * allocations on construction, and avoid extra allocations if [midpoint] is called multiple
- * times. Although the Immutable lint is being suppressed, this object is still immutable as its
- * visible data cannot be modified.
- */
- @Suppress("Immutable") private var _midpoint: ImmutableVec? = null
-
- override val midpoint: ImmutableVec
- get() =
- _midpoint
- ?: ImmutableVec((start.x + end.x) / 2, (start.y + end.y) / 2).also {
- _midpoint = it
- }
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) override fun asImmutable(): ImmutableSegment = this
override fun equals(other: Any?): Boolean =
- other === this || (other is Segment && Segment.areEquivalent(this, other))
+ other === this || (other is Segment && areEquivalent(this, other))
// NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode
- override fun hashCode(): Int = Segment.hash(this)
+ override fun hashCode(): Int = hash(this)
- override fun toString(): String = "Immutable${Segment.string(this)}"
+ override fun toString(): String = "Immutable${string(this)}"
}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableTriangle.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableTriangle.kt
index c99da80..183bb8e 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableTriangle.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableTriangle.kt
@@ -22,33 +22,23 @@
* An immutable triangle, defined by its three corners [p0], [p1] and [p2] in order. This object is
* immutable, so it is inherently thread-safe. See [MutableTriangle] for the mutable version.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public class ImmutableTriangle(p0: Vec, p1: Vec, p2: Vec) : Triangle {
+public class ImmutableTriangle(p0: Vec, p1: Vec, p2: Vec) : Triangle() {
- @Suppress("Immutable") override val p0: Vec = p0.asImmutable
- @Suppress("Immutable") override val p1: Vec = p1.asImmutable
- @Suppress("Immutable") override val p2: Vec = p2.asImmutable
+ @Suppress("Immutable") override val p0: Vec = p0.asImmutable()
+ @Suppress("Immutable") override val p1: Vec = p1.asImmutable()
+ @Suppress("Immutable") override val p2: Vec = p2.asImmutable()
- override fun asImmutable(): ImmutableTriangle = this
-
- @JvmSynthetic
- override fun asImmutable(p0: Vec, p1: Vec, p2: Vec): ImmutableTriangle {
- if (this.p0 === p0 && this.p1 === p1 && this.p2 === p2) {
- return this
- }
-
- return ImmutableTriangle(p0, p1, p2)
- }
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) override fun asImmutable(): ImmutableTriangle = this
/**
* Equality for [ImmutableTriangle] is defined using the order in which [p0], [p1] and [p2] are
* defined. Rotated/flipped triangles with out-of-order vertices are not considered equal.
*/
override fun equals(other: Any?): Boolean =
- other === this || (other is Triangle && Triangle.areEquivalent(this, other))
+ other === this || (other is Triangle && areEquivalent(this, other))
// NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode.
- override fun hashCode(): Int = Triangle.hash(this)
+ override fun hashCode(): Int = hash(this)
- override fun toString(): String = "Immutable${Triangle.string(this)}"
+ override fun toString(): String = "Immutable${string(this)}"
}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableVec.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableVec.kt
index fe0b830..01b29ec 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableVec.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ImmutableVec.kt
@@ -17,48 +17,29 @@
package androidx.ink.geometry
import androidx.annotation.RestrictTo
-import kotlin.jvm.JvmSynthetic
import kotlin.math.cos
-import kotlin.math.hypot
import kotlin.math.sin
/**
- * An immutable 2-dimensional vector, representing an offset in space. See [MutableVec] for a
- * mutable alternative, and see [Point] (and its concrete implementations [ImmutablePoint] and
- * [MutablePoint]) for a location in space.
+ * An immutable two-dimensional vector, i.e. an (x, y) coordinate pair. It can be used to represent
+ * either:
+ * 1) A two-dimensional offset, i.e. the difference between two points
+ * 2) A point in space, i.e. treating the vector as an offset from the origin
+ *
+ * This object is immutable, so it is inherently thread-safe. See [MutableVec] for a mutable
+ * alternative.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public class ImmutableVec(override val x: Float, override val y: Float) : Vec {
+public class ImmutableVec(override val x: Float, override val y: Float) : Vec() {
- /** Fills [output] with the x and y coordinates of this [ImmutableVec] */
- public fun fillMutable(output: MutableVec) {
- output.x = this.x
- output.y = this.y
- }
-
- /** Returns a [MutableVec] containing the same x and y coordinates as this [ImmutableVec] */
- public fun newMutable(): MutableVec {
- return MutableVec(x, y)
- }
-
- override val magnitude: Float = hypot(x, y)
-
- override val magnitudeSquared: Float = x * x + y * y
-
- override val asImmutable: ImmutableVec = this
-
- @JvmSynthetic
- override fun asImmutable(x: Float, y: Float): ImmutableVec {
- return if (x == this.x && y == this.y) this else ImmutableVec(x, y)
- }
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) override fun asImmutable(): ImmutableVec = this
override fun equals(other: Any?): Boolean =
- other === this || (other is Vec && Vec.areEquivalent(this, other))
+ other === this || (other is Vec && areEquivalent(this, other))
// NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode
- override fun hashCode(): Int = Vec.hash(this)
+ override fun hashCode(): Int = hash(this)
- override fun toString(): String = "Immutable${Vec.string(this)}"
+ override fun toString(): String = "Immutable${string(this)}"
public companion object {
@JvmStatic
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Intersection.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Intersection.kt
index ce3fce9..1ee275d 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Intersection.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Intersection.kt
@@ -23,7 +23,6 @@
* Contains functions for intersection of ink geometry classes. For Kotlin callers, these are
* available as extension functions on the geometry classes themselves.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
public object Intersection {
init {
@@ -117,17 +116,18 @@
* intersection of the point in [mesh]’s object coordinates.
*/
@JvmStatic
- public fun Vec.intersects(mesh: ModeledShape, meshToPoint: AffineTransform): Boolean {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public fun Vec.intersects(mesh: PartitionedMesh, meshToPoint: AffineTransform): Boolean {
return nativeMeshVecIntersects(
nativeMeshAddress = mesh.getNativeAddress(),
vecX = this.x,
vecY = this.y,
- meshToVecA = meshToPoint.a,
- meshToVecB = meshToPoint.b,
- meshToVecC = meshToPoint.c,
- meshToVecD = meshToPoint.d,
- meshToVecE = meshToPoint.e,
- meshToVecF = meshToPoint.f,
+ meshToVecA = meshToPoint.m00,
+ meshToVecB = meshToPoint.m10,
+ meshToVecC = meshToPoint.m20,
+ meshToVecD = meshToPoint.m01,
+ meshToVecE = meshToPoint.m11,
+ meshToVecF = meshToPoint.m21,
)
}
@@ -218,19 +218,20 @@
* coordinate space to the coordinate space that the intersection should be checked in.
*/
@JvmStatic
- public fun Segment.intersects(mesh: ModeledShape, meshToSegment: AffineTransform): Boolean {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public fun Segment.intersects(mesh: PartitionedMesh, meshToSegment: AffineTransform): Boolean {
return nativeMeshSegmentIntersects(
nativeMeshAddress = mesh.getNativeAddress(),
segmentStartX = this.start.x,
segmentStartY = this.start.y,
segmentEndX = this.end.x,
segmentEndY = this.end.y,
- meshToSegmentA = meshToSegment.a,
- meshToSegmentB = meshToSegment.b,
- meshToSegmentC = meshToSegment.c,
- meshToSegmentD = meshToSegment.d,
- meshToSegmentE = meshToSegment.e,
- meshToSegmentF = meshToSegment.f,
+ meshToSegmentA = meshToSegment.m00,
+ meshToSegmentB = meshToSegment.m10,
+ meshToSegmentC = meshToSegment.m20,
+ meshToSegmentD = meshToSegment.m01,
+ meshToSegmentE = meshToSegment.m11,
+ meshToSegmentF = meshToSegment.m21,
)
}
@@ -310,7 +311,11 @@
* coordinate space to the coordinate space that the intersection should be checked in.
*/
@JvmStatic
- public fun Triangle.intersects(mesh: ModeledShape, meshToTriangle: AffineTransform): Boolean {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public fun Triangle.intersects(
+ mesh: PartitionedMesh,
+ meshToTriangle: AffineTransform
+ ): Boolean {
return nativeMeshTriangleIntersects(
nativeMeshAddress = mesh.getNativeAddress(),
triangleP0X = this.p0.x,
@@ -319,12 +324,12 @@
triangleP1Y = this.p1.y,
triangleP2X = this.p2.x,
triangleP2Y = this.p2.y,
- meshToTriangleA = meshToTriangle.a,
- meshToTriangleB = meshToTriangle.b,
- meshToTriangleC = meshToTriangle.c,
- meshToTriangleD = meshToTriangle.d,
- meshToTriangleE = meshToTriangle.e,
- meshToTriangleF = meshToTriangle.f,
+ meshToTriangleA = meshToTriangle.m00,
+ meshToTriangleB = meshToTriangle.m10,
+ meshToTriangleC = meshToTriangle.m20,
+ meshToTriangleD = meshToTriangle.m01,
+ meshToTriangleE = meshToTriangle.m11,
+ meshToTriangleF = meshToTriangle.m21,
)
}
@@ -377,19 +382,20 @@
* coordinate space to the coordinate space that the intersection should be checked in.
*/
@JvmStatic
- public fun Box.intersects(mesh: ModeledShape, meshToBox: AffineTransform): Boolean {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public fun Box.intersects(mesh: PartitionedMesh, meshToBox: AffineTransform): Boolean {
return nativeMeshBoxIntersects(
nativeMeshAddress = mesh.getNativeAddress(),
boxXMin = this.xMin,
boxYMin = this.yMin,
boxXMax = this.xMax,
boxYMax = this.yMax,
- meshToBoxA = meshToBox.a,
- meshToBoxB = meshToBox.b,
- meshToBoxC = meshToBox.c,
- meshToBoxD = meshToBox.d,
- meshToBoxE = meshToBox.e,
- meshToBoxF = meshToBox.f,
+ meshToBoxA = meshToBox.m00,
+ meshToBoxB = meshToBox.m10,
+ meshToBoxC = meshToBox.m20,
+ meshToBoxD = meshToBox.m01,
+ meshToBoxE = meshToBox.m11,
+ meshToBoxF = meshToBox.m21,
)
}
@@ -430,8 +436,9 @@
* checked in.
*/
@JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
public fun Parallelogram.intersects(
- mesh: ModeledShape,
+ mesh: PartitionedMesh,
meshToParallelogram: AffineTransform,
): Boolean {
return nativeMeshParallelogramIntersects(
@@ -442,12 +449,12 @@
parallelogramHeight = this.height,
parallelogramAngleInRadian = this.rotation,
parallelogramShearFactor = this.shearFactor,
- meshToParallelogramA = meshToParallelogram.a,
- meshToParallelogramB = meshToParallelogram.b,
- meshToParallelogramC = meshToParallelogram.c,
- meshToParallelogramD = meshToParallelogram.d,
- meshToParallelogramE = meshToParallelogram.e,
- meshToParallelogramF = meshToParallelogram.f,
+ meshToParallelogramA = meshToParallelogram.m00,
+ meshToParallelogramB = meshToParallelogram.m10,
+ meshToParallelogramC = meshToParallelogram.m20,
+ meshToParallelogramD = meshToParallelogram.m01,
+ meshToParallelogramE = meshToParallelogram.m11,
+ meshToParallelogramF = meshToParallelogram.m21,
)
}
@@ -460,26 +467,27 @@
* coordinate space that the intersection should be checked in.
*/
@JvmStatic
- public fun ModeledShape.intersects(
- other: ModeledShape,
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public fun PartitionedMesh.intersects(
+ other: PartitionedMesh,
thisToCommonTransForm: AffineTransform,
otherToCommonTransform: AffineTransform,
): Boolean {
return nativeMeshModeledShapeIntersects(
thisModeledShapeAddress = this.getNativeAddress(),
otherModeledShapeAddress = other.getNativeAddress(),
- thisToCommonTransformA = thisToCommonTransForm.a,
- thisToCommonTransformB = thisToCommonTransForm.b,
- thisToCommonTransformC = thisToCommonTransForm.c,
- thisToCommonTransformD = thisToCommonTransForm.d,
- thisToCommonTransformE = thisToCommonTransForm.e,
- thisToCommonTransformF = thisToCommonTransForm.f,
- otherToCommonTransformA = otherToCommonTransform.a,
- otherToCommonTransformB = otherToCommonTransform.b,
- otherToCommonTransformC = otherToCommonTransform.c,
- otherToCommonTransformD = otherToCommonTransform.d,
- otherToCommonTransformE = otherToCommonTransform.e,
- otherToCommonTransformF = otherToCommonTransform.f,
+ thisToCommonTransformA = thisToCommonTransForm.m00,
+ thisToCommonTransformB = thisToCommonTransForm.m10,
+ thisToCommonTransformC = thisToCommonTransForm.m20,
+ thisToCommonTransformD = thisToCommonTransForm.m01,
+ thisToCommonTransformE = thisToCommonTransForm.m11,
+ thisToCommonTransformF = thisToCommonTransForm.m21,
+ otherToCommonTransformA = otherToCommonTransform.m00,
+ otherToCommonTransformB = otherToCommonTransform.m10,
+ otherToCommonTransformC = otherToCommonTransform.m20,
+ otherToCommonTransformD = otherToCommonTransform.m01,
+ otherToCommonTransformE = otherToCommonTransform.m11,
+ otherToCommonTransformF = otherToCommonTransform.m21,
)
}
@@ -558,7 +566,8 @@
* intersection of the point in [mesh]’s object coordinates.
*/
@JvmStatic
- public fun ModeledShape.intersects(point: Vec, meshToPoint: AffineTransform): Boolean =
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public fun PartitionedMesh.intersects(point: Vec, meshToPoint: AffineTransform): Boolean =
point.intersects(this, meshToPoint)
/**
@@ -569,8 +578,11 @@
* coordinate space to the coordinate space that the intersection should be checked in.
*/
@JvmStatic
- public fun ModeledShape.intersects(segment: Segment, meshToSegment: AffineTransform): Boolean =
- segment.intersects(this, meshToSegment)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public fun PartitionedMesh.intersects(
+ segment: Segment,
+ meshToSegment: AffineTransform
+ ): Boolean = segment.intersects(this, meshToSegment)
/**
* Returns true when a [PartitionedMesh] intersects with a [Triangle].
@@ -580,9 +592,10 @@
* coordinate space to the coordinate space that the intersection should be checked in.
*/
@JvmStatic
- public fun ModeledShape.intersects(
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public fun PartitionedMesh.intersects(
triangle: Triangle,
- meshToTriangle: AffineTransform
+ meshToTriangle: AffineTransform,
): Boolean = triangle.intersects(this, meshToTriangle)
/**
@@ -593,7 +606,8 @@
* coordinate space to the coordinate space that the intersection should be checked in.
*/
@JvmStatic
- public fun ModeledShape.intersects(box: Box, meshToBox: AffineTransform): Boolean =
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public fun PartitionedMesh.intersects(box: Box, meshToBox: AffineTransform): Boolean =
box.intersects(this, meshToBox)
/**
@@ -605,7 +619,8 @@
* checked in.
*/
@JvmStatic
- public fun ModeledShape.intersects(
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
+ public fun PartitionedMesh.intersects(
parallelogram: Parallelogram,
meshToParallelogram: AffineTransform,
): Boolean = parallelogram.intersects(this, meshToParallelogram)
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Mesh.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Mesh.kt
index 5c3e74f..e3959af 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Mesh.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Mesh.kt
@@ -135,7 +135,7 @@
* [vertexCount]). The resulting x/y position of that vertex will be put into [outPosition],
* which can be pre-allocated and reused to avoid allocations where appropriate.
*/
- public fun fillPosition(@IntRange(from = 0) vertexIndex: Int, outPosition: MutablePoint) {
+ public fun fillPosition(@IntRange(from = 0) vertexIndex: Int, outPosition: MutableVec) {
require(vertexIndex >= 0 && vertexIndex < vertexCount) {
"vertexIndex=$vertexIndex must be between 0 and vertexCount=$vertexCount."
}
@@ -239,7 +239,7 @@
external fun fillPosition(
nativeAddress: Long,
vertexIndex: Int,
- outPosition: MutablePoint
+ outPosition: MutableVec
) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
@VisibleForTesting
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableAffineTransform.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableAffineTransform.kt
index c1c9572..26bce28 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableAffineTransform.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableAffineTransform.kt
@@ -17,22 +17,22 @@
package androidx.ink.geometry
import androidx.annotation.RestrictTo
+import androidx.annotation.Size
/**
- * A mutable affine transformation in the plane. The transformation can be thought of as a 3x3
- * matrix:
+ * An affine transformation in the plane. The transformation can be thought of as a 3x3 matrix:
* ```
- * ⎡a b c⎤
- * ⎢d e f⎥
- * ⎣0 0 1⎦
+ * ⎡m00 m10 m20⎤
+ * ⎢m01 m11 m21⎥
+ * ⎣ 0 0 1 ⎦
* ```
*
* Applying the transformation can be thought of as a matrix multiplication, with the
* to-be-transformed point represented as a column vector with an extra 1:
* ```
- * ⎡a b c⎤ ⎡x⎤ ⎡a*x + b*y + c⎤
- * ⎢d e f⎥ * ⎢y⎥ = ⎢d*x + e*y + f⎥
- * ⎣0 0 1⎦ ⎣1⎦ ⎣ 1 ⎦
+ * ⎡m00 m10 m20⎤ ⎡x⎤ ⎡m00*x + m10*y + m20⎤
+ * ⎢m01 m11 m21⎥ * ⎢y⎥ = ⎢m01*x + m11*y + m21⎥
+ * ⎣ 0 0 1 ⎦ ⎣1⎦ ⎣ 1 ⎦
* ```
*
* Transformations are composed via multiplication. Multiplication is not commutative (i.e. A*B !=
@@ -43,23 +43,42 @@
* val translate = ImmutableAffineTransform.translate(Vec(10, 0))
* ```
*
- * then the `rotate * translate` first translates 10 units in the positive x-direction, then rotates
- * 90° about the origin.
+ * then `rotate * translate` first translates 10 units in the positive x-direction, then rotates 45°
+ * about the origin.
*
- * This class follows AndroidX guidelines ({@link http://go/androidx-api-guidelines#kotlin-data}) to
- * avoid Kotlin data classes.
+ * See [ImmutableAffineTransform] for an immutable alternative to this class.
*
- * See [ImmutableAffineTransform] for immutable alternative to this class.
+ * @constructor Constructs this transform with 6 float values, starting with the top left corner of
+ * the matrix and proceeding in row-major order. Prefer to create this object with functions that
+ * apply specific transform operations, such as [populateFromScale] or [populateFromRotate],
+ * rather than directly passing in the actual numeric values of this transform. This constructor
+ * is useful for when the values are needed to be provided all at once, for example for
+ * serialization. To access these values in the same order as they are passed in here, use
+ * [AffineTransform.getValues]. To construct this object using an array as input, there is another
+ * public constructor for that.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public class MutableAffineTransform(
- override var a: Float,
- override var b: Float,
- override var c: Float,
- override var d: Float,
- override var e: Float,
- override var f: Float,
-) : AffineTransform {
+public class MutableAffineTransform
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public constructor(
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override var m00: Float,
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override var m10: Float,
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override var m20: Float,
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override var m01: Float,
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override var m11: Float,
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
+ override var m21: Float,
+) : AffineTransform() {
/**
* Constructs an identity [MutableAffineTransform]:
@@ -69,10 +88,44 @@
* ⎣0 0 1⎦
* ```
*
- * This is useful when pre-allocating a scratch instance to be filled later. (e.g. using
- * [ImmutableAffineTransform.fillMutable] method)
+ * This is useful when pre-allocating a scratch instance to be filled later.
*/
- public constructor() : this(a = 1f, b = 0f, c = 0f, d = 0f, e = 1f, f = 0f)
+ public constructor() : this(1f, 0f, 0f, 0f, 1f, 0f)
+
+ /**
+ * Populates this transform with the given values, starting with the top left corner of the
+ * matrix and proceeding in row-major order.
+ *
+ * Prefer to modify this object with functions that apply specific transform operations, such as
+ * [populateFromScale] or [populateFromRotate], rather than directly setting the actual numeric
+ * values of this transform. This function is useful for when the values are needed to be
+ * provided in bulk, for example for serialization.
+ *
+ * To access these values in the same order as they are set here, use
+ * [AffineTransform.getValues].
+ */
+ public fun setValues(m00: Float, m10: Float, m20: Float, m01: Float, m11: Float, m21: Float) {
+ this.m00 = m00
+ this.m10 = m10
+ this.m20 = m20
+ this.m01 = m01
+ this.m11 = m11
+ this.m21 = m21
+ }
+
+ /** Like [setValues], but accepts a [FloatArray] instead of individual float values. */
+ public fun setValues(@Size(min = 6) values: FloatArray) {
+ m00 = values[0]
+ m10 = values[1]
+ m20 = values[2]
+ m01 = values[3]
+ m11 = values[4]
+ m21 = values[5]
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ override fun asImmutable(): ImmutableAffineTransform =
+ ImmutableAffineTransform(m00, m10, m20, m01, m11, m21)
/**
* Component-wise equality operator for [MutableAffineTransform].
@@ -82,10 +135,10 @@
* [equals] in some cases.
*/
override fun equals(other: Any?): Boolean =
- other === this || (other is AffineTransform && AffineTransform.areEquivalent(this, other))
+ other === this || (other is AffineTransform && areEquivalent(this, other))
// NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode
- override fun hashCode(): Int = AffineTransform.hash(this)
+ override fun hashCode(): Int = hash(this)
- override fun toString(): String = "Mutable${AffineTransform.hash(this)}"
+ override fun toString(): String = "Mutable${string(this)}"
}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableBox.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableBox.kt
index 9a417da..9721bfc 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableBox.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableBox.kt
@@ -17,7 +17,6 @@
package androidx.ink.geometry
import androidx.annotation.FloatRange
-import androidx.annotation.RestrictTo
import kotlin.math.max
import kotlin.math.min
@@ -28,8 +27,7 @@
* (e.g. the positive Y axis being "down"), because it is intended to be used with any coordinate
* system rather than just Android screen/View space.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public class MutableBox private constructor(x1: Float, y1: Float, x2: Float, y2: Float) : Box {
+public class MutableBox private constructor(x1: Float, y1: Float, x2: Float, y2: Float) : Box() {
/** The lower bound in the `X` direction. */
override var xMin: Float = min(x1, x2)
@@ -47,10 +45,6 @@
override var yMax: Float = max(y1, y2)
private set
- /** Populates [out] with the center of the [MutableBox]. */
- override fun center(out: MutablePoint): Unit =
- BoxHelper.nativeCenter(xMin, yMin, xMax, yMax, out)
-
/**
* Sets the lower and upper bounds in the `X` direction to new values. The minimum value becomes
* `xMin`, and the maximum value becomes `xMax`. Returns the same instance to chain function
@@ -80,7 +74,7 @@
public constructor() : this(0f, 0f, 0f, 0f)
/** Constructs the smallest [MutableBox] containing the two given points. */
- public fun fillFromTwoPoints(point1: Point, point2: Point): MutableBox {
+ public fun populateFromTwoPoints(point1: Vec, point2: Vec): MutableBox {
setXBounds(point1.x, point2.x)
setYBounds(point1.y, point2.y)
return this
@@ -90,8 +84,8 @@
* Constructs a [MutableBox] with a given [center], [width], and [height]. [width] and [height]
* must be non-negative numbers.
*/
- public fun fillFromCenterAndDimensions(
- center: Point,
+ public fun populateFromCenterAndDimensions(
+ center: Vec,
@FloatRange(from = 0.0) width: Float,
@FloatRange(from = 0.0) height: Float,
): MutableBox {
@@ -110,16 +104,6 @@
return this
}
- /** Convert this object to a new immutable [Box]. */
- public fun buildBox(): ImmutableBox {
- return ImmutableBox.fromTwoPoints(ImmutablePoint(xMin, yMin), ImmutablePoint(xMax, yMax))
- }
-
- /** Return a copy of this object that can be modified independently. */
- public fun copy(): MutableBox {
- return MutableBox(xMin, yMin, xMax, yMax)
- }
-
override fun equals(other: Any?): Boolean =
other === this || (other is Box && Box.areEquivalent(this, other))
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableParallelogram.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableParallelogram.kt
index e899efa..c700fc1 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableParallelogram.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableParallelogram.kt
@@ -22,34 +22,33 @@
/**
* Mutable parallelogram (i.e. a quadrilateral with parallel sides), defined by its [center],
* [width], [height], [rotation], and [shearFactor].
+ *
+ * @constructor Create the [MutableParallelogram] from an existing [MutableVec] instance and
+ * primitive [Float] parameters. Note that this instances will become the internal state of this
+ * [MutableParallelogram], so modifications made to it directly or through setters on this
+ * [MutableParallelogram] will modify the input [MutableVec] instances too. This is to allow
+ * performance-critical code to avoid any unnecessary allocations. This can be tricky to manage,
+ * especially in multithreaded code, so when calling code is unable to guarantee ownership of the
+ * nested mutable data at a particular time, it may be safest to construct this with a copy of the
+ * data to give this [MutableSegment] exclusive ownership of that copy.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
public class MutableParallelogram
private constructor(
- center: Point,
+ override var center: MutableVec,
width: Float,
override var height: Float,
@AngleRadiansFloat rotation: Float,
override var shearFactor: Float,
-) : Parallelogram {
+) : Parallelogram() {
- /* [_center] is a private backing field that is internally constructed such that no
- * caller can obtain a direct reference to it. */
- private var _center: MutablePoint = MutablePoint(center.x, center.y)
@AngleRadiansFloat private var _rotation: Float = Angle.normalized(rotation)
+
override var rotation: Float
@AngleRadiansFloat get() = _rotation
set(@AngleRadiansFloat value) {
_rotation = Angle.normalized(value)
}
- override var center: Point
- get() = _center
- set(value) {
- _center.x = value.x
- _center.y = value.y
- }
-
private var _width: Float = width
override var width: Float
@FloatRange(from = 0.0) get() = _width
@@ -57,7 +56,7 @@
// A [Parallelogram] may *not* have a negative width. If an operation is performed on
// [Parallelogram] resulting
// in a negative width, it will be normalized.
- Parallelogram.normalizeAndRun(value, height, rotation) { w: Float, h: Float, r: Float ->
+ normalizeAndRun(value, height, rotation) { w: Float, h: Float, r: Float ->
_width = w
height = h
rotation = r
@@ -65,7 +64,7 @@
}
}
- public constructor() : this(ImmutablePoint(0f, 0f), 0f, 0f, Angle.ZERO, 0f)
+ public constructor() : this(MutableVec(), 0f, 0f, Angle.ZERO, 0f)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
// TODO: b/355248266 - @UsedByNative("parallelogram_jni_helper.cc") must go in Proguard config
@@ -78,13 +77,13 @@
@AngleRadiansFloat rotation: Float,
shearFactor: Float,
): Unit = run {
- Parallelogram.normalizeAndRun(width, height, rotation) { w: Float, h: Float, r: Float ->
+ normalizeAndRun(width, height, rotation) { w: Float, h: Float, r: Float ->
this.width = w
this.height = h
this.rotation = r
this.shearFactor = shearFactor
- this._center.x = centerX
- this._center.y = centerY
+ this.center.x = centerX
+ this.center.y = centerY
this
}
}
@@ -93,9 +92,9 @@
other === this || (other is Parallelogram && Parallelogram.areEquivalent(this, other))
// NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode
- override fun hashCode(): Int = Parallelogram.hash(this)
+ override fun hashCode(): Int = hash(this)
- override fun toString(): String = "Mutable${Parallelogram.string(this)}"
+ override fun toString(): String = "Mutable${string(this)}"
public companion object {
@@ -106,14 +105,11 @@
*/
@JvmStatic
public fun fromCenterAndDimensions(
- center: Point,
+ center: MutableVec,
@FloatRange(from = 0.0) width: Float,
height: Float,
): MutableParallelogram =
- Parallelogram.normalizeAndRun(width, height, rotation = Angle.ZERO) {
- w: Float,
- h: Float,
- r: Float ->
+ normalizeAndRun(width, height, rotation = Angle.ZERO) { w: Float, h: Float, r: Float ->
MutableParallelogram(center, w, h, r, shearFactor = 0f)
}
@@ -125,12 +121,12 @@
*/
@JvmStatic
public fun fromCenterDimensionsAndRotation(
- center: Point,
+ center: MutableVec,
@FloatRange(from = 0.0) width: Float,
height: Float,
@AngleRadiansFloat rotation: Float,
): MutableParallelogram =
- Parallelogram.normalizeAndRun(width, height, rotation) { w: Float, h: Float, r: Float ->
+ normalizeAndRun(width, height, rotation) { w: Float, h: Float, r: Float ->
MutableParallelogram(center, w, h, r, shearFactor = 0f)
}
@@ -141,13 +137,13 @@
*/
@JvmStatic
public fun fromCenterDimensionsRotationAndShear(
- center: Point,
+ center: MutableVec,
@FloatRange(from = 0.0) width: Float,
height: Float,
@AngleRadiansFloat rotation: Float,
shearFactor: Float,
): MutableParallelogram =
- Parallelogram.normalizeAndRun(width, height, rotation) { w: Float, h: Float, r: Float ->
+ normalizeAndRun(width, height, rotation) { w: Float, h: Float, r: Float ->
MutableParallelogram(center, w, h, r, shearFactor)
}
}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutablePoint.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutablePoint.kt
deleted file mode 100644
index 8308e1b..0000000
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutablePoint.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2024 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.ink.geometry
-
-import androidx.annotation.RestrictTo
-
-/**
- * Represents a location in 2-dimensional space. See [ImmutablePoint] for an immutable alternative.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public class MutablePoint(
- override var x:
- Float, // TODO: b/355248266 - @set:UsedByNative("point_jni_helper.cc") must go in Proguard
- // config file instead.
- override var y:
- Float, // TODO: b/355248266 - @set:UsedByNative("point_jni_helper.cc") must go in Proguard
- // config file instead.
-) : Point {
-
- /**
- * Constructs a [MutablePoint] without any initial data. This is useful when pre-allocating an
- * instance to be filled later.
- */
- public constructor() : this(0f, 0f)
-
- /** Construct an [ImmutablePoint] out of this [MutablePoint]. */
- public fun build(): ImmutablePoint = ImmutablePoint(x, y)
-
- override fun equals(other: Any?): Boolean =
- other === this || (other is Point && Point.areEquivalent(this, other))
-
- // NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode
- override fun hashCode(): Int = Point.hash(this)
-
- override fun toString(): String = "Mutable${Point.string(this)}"
-}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableSegment.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableSegment.kt
index ecade29..de47366 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableSegment.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableSegment.kt
@@ -21,77 +21,39 @@
/**
* Represents a mutable directed line segment between two points. See [ImmutableSegment] for the
* immutable alternative.
+ *
+ * @constructor Create the [MutableSegment] from two existing [MutableVec] instances. Note that
+ * these instances will become the internal state of this [MutableSegment], so modifications made
+ * to them directly or through setters on this [MutableSegment] will modify the input [MutableVec]
+ * instances too. This is to allow performance-critical code to avoid any unnecessary allocations.
+ * This can be tricky to manage, especially in multithreaded code, so when calling code is unable
+ * to guarantee ownership of the nested mutable data at a particular time, it may be safest to
+ * construct this with copies of the data to give this [MutableSegment] exclusive ownership of
+ * those copies.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public class MutableSegment(start: Vec, end: Vec) : Segment {
-
- private var _start = MutableVec(start.x, start.y)
- private var _end = MutableVec(end.x, end.y)
-
- override var start: Vec = _start
- private set
-
- override var end: Vec = _end
- private set
+public class MutableSegment(override var start: MutableVec, override var end: MutableVec) :
+ Segment() {
/** Constructs a degenerate [MutableSegment] with both [start] and [end] set to (0f, 0f) */
public constructor() : this(MutableVec(0f, 0f), MutableVec(0f, 0f))
- /** Sets this segment’s [start] point. */
- public fun start(point: Vec): MutableSegment {
- this._start.x = point.x
- this._start.y = point.y
- return this
- }
-
- /** Sets this segment's [start] point to ([x], [y]). */
- public fun start(x: Float, y: Float): MutableSegment {
- this._start.x = x
- this._start.y = y
- return this
- }
-
- /** Sets this segment’s [end] point. */
- public fun end(point: Vec): MutableSegment {
- this._end.x = point.x
- this._end.y = point.y
- return this
- }
-
- /** Sets this segment's [end] point to ([x], [y]). */
- public fun end(x: Float, y: Float): MutableSegment {
- this._end.x = x
- this._end.y = y
- return this
- }
-
- /** Fills this [MutableSegment] with the same values contained in [input]. */
+ /** Fills this [MutableSegment] with the same values contained in [input] and returns `this`. */
public fun populateFrom(input: Segment): MutableSegment {
- this._start.x = input.start.x
- this._start.y = input.start.y
- this._end.x = input.end.x
- this._end.y = input.end.y
+ this.start.x = input.start.x
+ this.start.y = input.start.y
+ this.end.x = input.end.x
+ this.end.y = input.end.y
return this
}
- override val vec: ImmutableVec
- get() = ImmutableVec(end.x - start.x, end.y - start.y)
-
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asImmutable(): ImmutableSegment = ImmutableSegment(this.start, this.end)
- @JvmSynthetic
- override fun asImmutable(start: Vec, end: Vec): ImmutableSegment {
- return ImmutableSegment(start, end)
- }
-
- override val midpoint: ImmutableVec
- get() = ImmutableVec((start.x + end.x) / 2, (start.y + end.y) / 2)
-
override fun equals(other: Any?): Boolean =
- other === this || (other is Segment && Segment.areEquivalent(this, other))
+ other === this || (other is Segment && areEquivalent(this, other))
// NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode
- override fun hashCode(): Int = Segment.hash(this)
+ override fun hashCode(): Int = hash(this)
- override fun toString(): String = "Mutable${Segment.string(this)}"
+ override fun toString(): String = "Mutable${string(this)}"
}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableTriangle.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableTriangle.kt
index 05ac2b6..05e5598 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableTriangle.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableTriangle.kt
@@ -21,95 +21,48 @@
/**
* Represents a mutable triangle, defined by its three corners [p0], [p1] and [p2] in order. See
* [ImmutableTriangle] for the immutable version.
+ *
+ * @constructor Create the [MutableTriangle] from three existing [MutableVec] instances. Note that
+ * these instances will become the internal state of this [MutableTriangle], so modifications made
+ * to them directly or through setters on this [MutableTriangle] will modify the input
+ * [MutableVec] instances too. This is to allow performance-critical code to avoid any unnecessary
+ * allocations. This can be tricky to manage, especially in multithreaded code, so when calling
+ * code is unable to guarantee ownership of the nested mutable data at a particular time, it may
+ * be safest to construct this with copies of the data to give this [MutableTriangle] exclusive
+ * ownership of those copies.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public class MutableTriangle(p0: Vec, p1: Vec, p2: Vec) : Triangle {
-
- private var _p0 = MutableVec(p0.x, p0.y)
- private var _p1 = MutableVec(p1.x, p1.y)
- private var _p2 = MutableVec(p2.x, p2.y)
-
- override var p0: Vec = _p0
- private set
-
- override var p1: Vec = _p1
- private set
-
- override var p2: Vec = _p2
- private set
+public class MutableTriangle(
+ override var p0: MutableVec,
+ override var p1: MutableVec,
+ override var p2: MutableVec,
+) : Triangle() {
/** Constructs a degenerate [MutableTriangle] with [p0], [p1], and [p2] set to (0, 0). */
public constructor() : this(MutableVec(0f, 0f), MutableVec(0f, 0f), MutableVec(0f, 0f))
- /** Sets [p0] equal to [value]. */
- public fun p0(value: Vec): MutableTriangle {
- _p0.x = value.x
- _p0.y = value.y
- return this
- }
-
- /** Sets [p0] to the location ([x], [y]). */
- public fun p0(x: Float, y: Float): MutableTriangle {
- _p0.x = x
- _p0.y = y
- return this
- }
-
- /** Sets [p1] equal to [value]. */
- public fun p1(value: Vec): MutableTriangle {
- _p1.x = value.x
- _p1.y = value.y
- return this
- }
-
- /** Sets [p1] to the location ([x], [y]). */
- public fun p1(x: Float, y: Float): MutableTriangle {
- _p1.x = x
- _p1.y = y
- return this
- }
-
- /** Sets [p2] equal to [value]. */
- public fun p2(value: Vec): MutableTriangle {
- _p2.x = value.x
- _p2.y = value.y
- return this
- }
-
- /** Sets [p2] to the location ([x], [y]). */
- public fun p2(x: Float, y: Float): MutableTriangle {
- _p2.x = x
- _p2.y = y
- return this
- }
-
- /** Copies the points from [input] to [this] [MutableTriangle]. */
+ /** Copies the points from [input] to this [MutableTriangle] and returns `this`. */
public fun populateFrom(input: Triangle): MutableTriangle {
- _p0.x = input.p0.x
- _p0.y = input.p0.y
- _p1.x = input.p1.x
- _p1.y = input.p1.y
- _p2.x = input.p2.x
- _p2.y = input.p2.y
+ p0.x = input.p0.x
+ p0.y = input.p0.y
+ p1.x = input.p1.x
+ p1.y = input.p1.y
+ p2.x = input.p2.x
+ p2.y = input.p2.y
return this
}
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun asImmutable(): ImmutableTriangle = ImmutableTriangle(this.p0, this.p1, this.p2)
- @JvmSynthetic
- override fun asImmutable(p0: Vec, p1: Vec, p2: Vec): ImmutableTriangle {
- return ImmutableTriangle(p0, p1, p2)
- }
-
/**
* Equality for [MutableTriangle] is defined using the order in which [p0], [p1] and [p2] are
* defined. Rotated/flipped triangles with out-of-order vertices are not considered equal.
*/
override fun equals(other: Any?): Boolean =
- other === this || (other is Triangle && Triangle.areEquivalent(this, other))
+ other === this || (other is Triangle && areEquivalent(this, other))
// NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode
- override fun hashCode(): Int = Triangle.hash(this)
+ override fun hashCode(): Int = hash(this)
- override fun toString(): String = "Mutable${Triangle.string(this)}"
+ override fun toString(): String = "Mutable${string(this)}"
}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableVec.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableVec.kt
index e1d3d7a..81226b1 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableVec.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableVec.kt
@@ -16,54 +16,33 @@
package androidx.ink.geometry
-import androidx.annotation.FloatRange
import androidx.annotation.RestrictTo
import kotlin.math.cos
-import kotlin.math.hypot
import kotlin.math.sin
/**
- * A mutable 2-dimensional vector, representing an offset in space. See [ImmutableVec] for an
- * immutable alternative, and see [Point] (and its concrete implementations [ImmutablePoint] and
- * [MutablePoint]) for a location in space.
+ * A mutable two-dimensional vector, i.e. an (x, y) coordinate pair. It can be used to represent
+ * either:
+ * 1) A two-dimensional offset, i.e. the difference between two points
+ * 2) A point in space, i.e. treating the vector as an offset from the origin
+ *
+ * This object is mutable and is not inherently thread-safe, so callers should apply their own
+ * synchronization logic or use this object from a single thread. See [ImmutableVec] for an
+ * immutable alternative.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
public class MutableVec(
override var x:
- Float, // TODO: b/355248266 - @set:UsedByNative("vec_jni.cc") must go in Proguard config
- // file instead.
+ Float, // TODO: b/355248266 - @set:UsedByNative("vec_jni_helper.cc") must go in Proguard
+ // config file instead.
override var y:
- Float, // TODO: b/355248266 - @set:UsedByNative("vec_jni.cc") must go in Proguard config
- // file instead.
-) : Vec {
+ Float, // TODO: b/355248266 - @set:UsedByNative("vec_jni_helper.cc") must go in Proguard
+ // config file instead.
+) : Vec() {
- /**
- * Constructs a [MutableVec] without any initial data. This is useful when pre-allocating an
- * instance to be filled later.
- */
- public constructor() : this(0f, 0f)
+ public constructor() : this(0F, 0F)
- override val magnitude: Float
- @FloatRange(from = 0.0) get() = hypot(x, y)
-
- override val magnitudeSquared: Float
- @FloatRange(from = 0.0) get() = x * x + y * y
-
- override val asImmutable: ImmutableVec = ImmutableVec(x, y)
-
- @JvmSynthetic override fun asImmutable(x: Float, y: Float): ImmutableVec = ImmutableVec(x, y)
-
- /** Sets the value of [x]. */
- public fun x(value: Float): MutableVec {
- x = value
- return this
- }
-
- /** Sets the value of [y]. */
- public fun y(value: Float): MutableVec {
- y = value
- return this
- }
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ override fun asImmutable(): ImmutableVec = ImmutableVec(x, y)
/** Fills this [MutableVec] with the same values contained in [input]. */
public fun populateFrom(input: Vec): MutableVec {
@@ -73,12 +52,12 @@
}
override fun equals(other: Any?): Boolean =
- other === this || (other is Vec && Vec.areEquivalent(this, other))
+ other === this || (other is Vec && areEquivalent(this, other))
// NOMUTANTS -- not testing exact hashCode values, just that equality implies same hashCode
- override fun hashCode(): Int = Vec.hash(this)
+ override fun hashCode(): Int = hash(this)
- override fun toString(): String = "Mutable${Vec.string(this)}"
+ override fun toString(): String = "Mutable${string(this)}"
public companion object {
@JvmStatic
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Parallelogram.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Parallelogram.kt
index eb09035..629984b 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Parallelogram.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Parallelogram.kt
@@ -17,7 +17,6 @@
package androidx.ink.geometry
import androidx.annotation.FloatRange
-import androidx.annotation.RestrictTo
import kotlin.math.abs
/**
@@ -80,41 +79,38 @@
* axes, and hence might have a non-zero [rotation]). A [Box], an axis-aligned rectangle; is a
* [Parallelogram] with both [rotation] and [shearFactor] of zero.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public interface Parallelogram {
+public abstract class Parallelogram internal constructor() {
- public val center: Point
+ public abstract val center: Vec
/**
* A [Parallelogram] may *not* have a negative width. If an operation on a parallelogram would
* result in a negative width, it is instead normalized, by negating both the width and the
* height, adding π to the angle of rotation, and normalizing rotation to the range [0, 2π).
*/
- @get:FloatRange(from = 0.0) public val width: Float
+ @get:FloatRange(from = 0.0) public abstract val width: Float
/**
* A [Parallelogram] may have a positive or negative height; a positive height indicates that
* the angle from the first semi-axis to the second will also be positive.
*/
- public val height: Float
+ public abstract val height: Float
- @get:AngleRadiansFloat public val rotation: Float
+ @get:AngleRadiansFloat public abstract val rotation: Float
/**
* A [Parallelogram]] may have a positive or negative shear factor; a positive shear factor
* indicates a smaller absolute angle between the semi-axes (the shear factor is, in fact, the
* cotangent of that angle).
*/
- public val shearFactor: Float
+ public abstract val shearFactor: Float
/**
- * A [Parallelogram] may have a positive or negative height; a positive height indicates that
- * the angle from the first semi-axis to the second will also be positive. A [Parallelogram]]
- * may have a positive or negative shear factor; a positive shear factor indicates a smaller
- * absolute angle between the semi-axes (the shear factor is, in fact, the cotangent of that
- * angle).
+ * Returns the signed area of the [Parallelogram]. If either the width or the height is zero,
+ * this will be equal to zero; if the width is non-zero, then this will have the same sign as
+ * the height.
*/
- public fun signedArea(): Float = width * height
+ public fun computeSignedArea(): Float = width * height
public companion object {
/**
@@ -140,7 +136,7 @@
* [Parallelogram].
*/
internal fun areEquivalent(first: Parallelogram, second: Parallelogram): Boolean =
- Point.areEquivalent(first.center, second.center) &&
+ Vec.areEquivalent(first.center, second.center) &&
first.width == second.width &&
first.height == second.height &&
first.rotation == second.rotation &&
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ModeledShape.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/PartitionedMesh.kt
similarity index 72%
rename from ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ModeledShape.kt
rename to ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/PartitionedMesh.kt
index bd67136..d279937 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/ModeledShape.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/PartitionedMesh.kt
@@ -16,6 +16,7 @@
package androidx.ink.geometry
+import androidx.annotation.FloatRange
import androidx.annotation.IntRange
import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
@@ -24,54 +25,57 @@
import androidx.ink.nativeloader.NativeLoader
/**
- * A triangulated shape, consisting of zero or more non-empty [Mesh]es, which may be indexed for
- * faster geometric queries. These meshes are divided among zero or more "render groups"; all the
- * meshes in a render group must have the same [MeshFormat], and can thus be rendered together. A
- * [ModeledShape] also optionally carries one or more "outlines", which are (potentially incomplete)
- * traversals of the vertices in the meshes, which could be used e.g. for path-based rendering. Note
- * that these render groups and outlines are ignored for the purposes of geometric queries; they
- * exist only for rendering purposes.
+ * An immutable† complex shape expressed as a set of triangles. This is used to represent the shape
+ * of a stroke or other complex objects see [MeshCreation]. The mesh may be divided into multiple
+ * partitions, which enables certain brush effects (e.g. "multi-coat"), and allows ink to create
+ * strokes requiring greater than 216 triangles (which must be rendered in multiple passes).
*
- * This is not meant to be constructed directly by developers. The primary constructor is to have a
- * new instance of this class manage a native `ink::ModeledShape` instance created by another
- * Strokes API utility.
+ * A PartitionedMesh may optionally have one or more "outlines", which are polylines that traverse
+ * some or all of the vertices in the mesh; these are used for path-based rendering of strokes. This
+ * supports disjoint meshes such as dashed lines.
+ *
+ * PartitionedMesh provides fast intersection and coverage testing by use of an internal spatial
+ * index.
+ *
+ * † PartitionedMesh is technically not immutable, as the spatial index is lazily instantiated;
+ * however, from the perspective of a caller, its properties do not change over the course of its
+ * lifetime. The entire object is thread-safe.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
@Suppress("NotCloseable") // Finalize is only used to free the native peer.
-public class ModeledShape
-/** Only for use within the ink library. Constructs a [ModeledShape] from native pointer. */
+public class PartitionedMesh
+/** Only for use within the ink library. Constructs a [PartitionedMesh] from native pointer. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public constructor(
/**
* This is the raw pointer address of an `ink::ModeledShape` that has been heap allocated to be
- * owned solely by this JVM [ModeledShape] object. Although the `ink::ModeledShape` is owned
- * exclusively by this [ModeledShape] object, it may be a copy of another `ink::ModeledShape`,
- * where it has a copy of fairly lightweight metadata but shares ownership of the more
- * heavyweight `ink::Mesh` objects. This class is responsible for freeing the
+ * owned solely by this JVM [PartitionedMesh] object. Although the `ink::ModeledShape` is owned
+ * exclusively by this [PartitionedMesh] object, it may be a copy of another
+ * `ink::ModeledShape`, where it has a copy of fairly lightweight metadata but shares ownership
+ * of the more heavyweight `ink::Mesh` objects. This class is responsible for freeing the
* `ink::ModeledShape` through its [finalize] method.
*/
private var nativeAddress: Long
) {
/**
- * Only for use within the ink library. Returns the native pointer held by this [ModeledShape].
+ * Only for use within the ink library. Returns the native pointer held by this
+ * [PartitionedMesh].
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun getNativeAddress(): Long = nativeAddress
private val scratchIntArray by threadLocal { IntArray(2) }
/**
- * Only for tests - creates a new empty [ModeledShape]. Since a [ModeledShape] is immutable,
- * this serves no practical purpose outside of tests.
+ * Only for tests - creates a new empty [PartitionedMesh]. Since a [PartitionedMesh] is
+ * immutable, this serves no practical purpose outside of tests.
*/
@VisibleForTesting internal constructor() : this(ModeledShapeNative.alloc())
/**
- * Returns the number of render groups in this shape. Each mesh in the [ModeledShape] belongs to
- * exactly one render group, and all meshes in the same render group will have the same
- * [MeshFormat] (and can thus be rendered together). The render groups are numbered in z-order
- * (the group with index zero should be rendered on bottom; the group with the highest index
- * should be rendered on top).
+ * The number of render groups in this mesh. Each outline in the [PartitionedMesh] belongs to
+ * exactly one render group, which are numbered in z-order: the group with index zero should be
+ * rendered on bottom; the group with the highest index should be rendered on top.
*/
@IntRange(from = 0)
public val renderGroupCount: Int =
@@ -86,7 +90,10 @@
}
}
- /** The axis-aligned, rectangular region occupied by the [meshes] of this shape. */
+ /**
+ * The minimum bounding box of the [PartitionedMesh]. This will be null if the [PartitionedMesh]
+ * is empty.
+ */
public val bounds: Box? = run {
val envelope = BoxAccumulator()
for (meshes in meshesByGroup) {
@@ -128,8 +135,8 @@
}
/**
- * The number of vertices in the outline at index [outlineIndex], which can be up to (but not
- * including) [outlineCount].
+ * The number of vertices that are in the outline at index [outlineIndex], and within the render
+ * group at [groupIndex].
*/
@IntRange(from = 0)
public fun outlineVertexCount(
@@ -150,11 +157,11 @@
* [outlineVertexCount] with [outlineIndex]). The resulting x/y position of that outline vertex
* will be put into [outPosition], which can be pre-allocated and reused to avoid allocations.
*/
- public fun fillOutlinePosition(
+ public fun populateOutlinePosition(
@IntRange(from = 0) groupIndex: Int,
@IntRange(from = 0) outlineIndex: Int,
@IntRange(from = 0) outlineVertexIndex: Int,
- outPosition: MutablePoint,
+ outPosition: MutableVec,
) {
val outlineVertexCount = outlineVertexCount(groupIndex, outlineIndex)
require(outlineVertexIndex >= 0 && outlineVertexIndex < outlineVertexCount) {
@@ -173,27 +180,24 @@
mesh.fillPosition(meshVertexIndex, outPosition)
}
- override fun toString(): String {
- val address = java.lang.Long.toHexString(nativeAddress)
- return "ModeledShape(bounds=$bounds, meshesByGroup=$meshesByGroup, nativeAddress=$address)"
- }
-
/**
- * Computes an approximate measure of what portion of this [ModeledShape] is covered by or
+ * Computes an approximate measure of what portion of this [PartitionedMesh] is covered by or
* overlaps with [triangle]. This is calculated by finding the sum of areas of the triangles
* that intersect the given [triangle], and dividing that by the sum of the areas of all
- * triangles in the [ModeledShape], all in the [ModeledShape]'s coordinate space. Triangles in
- * the [ModeledShape] that overlap each other (e.g. in the case of a stroke that loops back over
- * itself) are counted individually. Note that, if any triangles have negative area (due to
- * winding, see [com.google.inputmethod.ink.Triangle.signedArea]), the absolute value of their
- * area will be used instead.
+ * triangles in the [PartitionedMesh], all in the [PartitionedMesh]'s coordinate space.
+ * Triangles in the [PartitionedMesh] that overlap each other (e.g. in the case of a stroke that
+ * loops back over itself) are counted individually. Note that, if any triangles have negative
+ * area (due to winding, see [com.google.inputmethod.ink.Triangle.signedArea]), the absolute
+ * value of their area will be used instead.
*
- * On an empty [ModeledShape], this will always return 0.
+ * On an empty [PartitionedMesh], this will always return 0.
*
* Optional argument [triangleToThis] contains the transform that maps from [triangle]'s
- * coordinate space to this [ModeledShape]'s coordinate space, which defaults to the [IDENTITY].
+ * coordinate space to this [PartitionedMesh]'s coordinate space, which defaults to the
+ * [IDENTITY].
*/
@JvmOverloads
+ @FloatRange(from = 0.0, to = 1.0)
public fun coverage(
triangle: Triangle,
triangleToThis: AffineTransform = AffineTransform.IDENTITY,
@@ -206,30 +210,31 @@
triangleP1Y = triangle.p1.y,
triangleP2X = triangle.p2.x,
triangleP2Y = triangle.p2.y,
- triangleToThisTransformA = triangleToThis.a,
- triangleToThisTransformB = triangleToThis.b,
- triangleToThisTransformC = triangleToThis.c,
- triangleToThisTransformD = triangleToThis.d,
- triangleToThisTransformE = triangleToThis.e,
- triangleToThisTransformF = triangleToThis.f,
+ triangleToThisTransformA = triangleToThis.m00,
+ triangleToThisTransformB = triangleToThis.m10,
+ triangleToThisTransformC = triangleToThis.m20,
+ triangleToThisTransformD = triangleToThis.m01,
+ triangleToThisTransformE = triangleToThis.m11,
+ triangleToThisTransformF = triangleToThis.m21,
)
/**
- * Computes an approximate measure of what portion of this [ModeledShape] is covered by or
+ * Computes an approximate measure of what portion of this [PartitionedMesh] is covered by or
* overlaps with [box]. This is calculated by finding the sum of areas of the triangles that
* intersect the given [box], and dividing that by the sum of the areas of all triangles in the
- * [ModeledShape], all in the [ModeledShape]'s coordinate space. Triangles in the [ModeledShape]
- * that overlap each other (e.g. in the case of a stroke that loops back over itself) are
- * counted individually. Note that, if any triangles have negative area (due to winding, see
- * [com.google.inputmethod.ink.Triangle.signedArea]), the absolute value of their area will be
- * used instead.
+ * [PartitionedMesh], all in the [PartitionedMesh]'s coordinate space. Triangles in the
+ * [PartitionedMesh] that overlap each other (e.g. in the case of a stroke that loops back over
+ * itself) are counted individually. Note that, if any triangles have negative area (due to
+ * winding, see [com.google.inputmethod.ink.Triangle.signedArea]), the absolute value of their
+ * area will be used instead.
*
- * On an empty [ModeledShape], this will always return 0.
+ * On an empty [PartitionedMesh], this will always return 0.
*
* Optional argument [boxToThis] contains the transform that maps from [box]'s coordinate space
- * to this [ModeledShape]'s coordinate space, which defaults to the [IDENTITY].
+ * to this [PartitionedMesh]'s coordinate space, which defaults to the [IDENTITY].
*/
@JvmOverloads
+ @FloatRange(from = 0.0, to = 1.0)
public fun coverage(box: Box, boxToThis: AffineTransform = AffineTransform.IDENTITY): Float =
ModeledShapeNative.modeledShapeBoxCoverage(
nativeAddress = nativeAddress,
@@ -237,31 +242,32 @@
boxYMin = box.yMin,
boxXMax = box.xMax,
boxYMax = box.yMax,
- boxToThisTransformA = boxToThis.a,
- boxToThisTransformB = boxToThis.b,
- boxToThisTransformC = boxToThis.c,
- boxToThisTransformD = boxToThis.d,
- boxToThisTransformE = boxToThis.e,
- boxToThisTransformF = boxToThis.f,
+ boxToThisTransformA = boxToThis.m00,
+ boxToThisTransformB = boxToThis.m10,
+ boxToThisTransformC = boxToThis.m20,
+ boxToThisTransformD = boxToThis.m01,
+ boxToThisTransformE = boxToThis.m11,
+ boxToThisTransformF = boxToThis.m21,
)
/**
- * Computes an approximate measure of what portion of this [ModeledShape] is covered by or
+ * Computes an approximate measure of what portion of this [PartitionedMesh] is covered by or
* overlaps with [parallelogram]. This is calculated by finding the sum of areas of the
* triangles that intersect the given [parallelogram], and dividing that by the sum of the areas
- * of all triangles in the [ModeledShape], all in the [ModeledShape]'s coordinate space.
- * Triangles in the [ModeledShape] that overlap each other (e.g. in the case of a stroke that
+ * of all triangles in the [PartitionedMesh], all in the [PartitionedMesh]'s coordinate space.
+ * Triangles in the [PartitionedMesh] that overlap each other (e.g. in the case of a stroke that
* loops back over itself) are counted individually. Note that, if any triangles have negative
* area (due to winding, see [com.google.inputmethod.ink.Triangle.signedArea]), the absolute
* value of their area will be used instead.
*
- * On an empty [ModeledShape], this will always return 0.
+ * On an empty [PartitionedMesh], this will always return 0.
*
* Optional argument [parallelogramToThis] contains the transform that maps from
- * [parallelogram]'s coordinate space to this [ModeledShape]'s coordinate space, which defaults
- * to the [IDENTITY].
+ * [parallelogram]'s coordinate space to this [PartitionedMesh]'s coordinate space, which
+ * defaults to the [IDENTITY].
*/
@JvmOverloads
+ @FloatRange(from = 0.0, to = 1.0)
public fun coverage(
parallelogram: Parallelogram,
parallelogramToThis: AffineTransform = AffineTransform.IDENTITY,
@@ -274,47 +280,49 @@
parallelogramHeight = parallelogram.height,
parallelogramAngleInRadian = parallelogram.rotation,
parallelogramShearFactor = parallelogram.shearFactor,
- parallelogramToThisTransformA = parallelogramToThis.a,
- parallelogramToThisTransformB = parallelogramToThis.b,
- parallelogramToThisTransformC = parallelogramToThis.c,
- parallelogramToThisTransformD = parallelogramToThis.d,
- parallelogramToThisTransformE = parallelogramToThis.e,
- parallelogramToThisTransformF = parallelogramToThis.f,
+ parallelogramToThisTransformA = parallelogramToThis.m00,
+ parallelogramToThisTransformB = parallelogramToThis.m10,
+ parallelogramToThisTransformC = parallelogramToThis.m20,
+ parallelogramToThisTransformD = parallelogramToThis.m01,
+ parallelogramToThisTransformE = parallelogramToThis.m11,
+ parallelogramToThisTransformF = parallelogramToThis.m21,
)
/**
- * Computes an approximate measure of what portion of this [ModeledShape] is covered by or
- * overlaps with the [other] [ModeledShape]. This is calculated by finding the sum of areas of
- * the triangles that intersect [other], and dividing that by the sum of the areas of all
- * triangles in the [ModeledShape], all in the [ModeledShape]'s coordinate space. Triangles in
- * the [ModeledShape] that overlap each other (e.g. in the case of a stroke that loops back over
- * itself) are counted individually. Note that, if any triangles have negative area (due to
- * winding, see [com.google.inputmethod.ink.Triangle.signedArea]), the absolute value of their
- * area will be used instead.
+ * Computes an approximate measure of what portion of this [PartitionedMesh] is covered by or
+ * overlaps with the [other] [PartitionedMesh]. This is calculated by finding the sum of areas
+ * of the triangles that intersect [other], and dividing that by the sum of the areas of all
+ * triangles in the [PartitionedMesh], all in the [PartitionedMesh]'s coordinate space.
+ * Triangles in the [PartitionedMesh] that overlap each other (e.g. in the case of a stroke that
+ * loops back over itself) are counted individually. Note that, if any triangles have negative
+ * area (due to winding, see [com.google.inputmethod.ink.Triangle.signedArea]), the absolute
+ * value of their area will be used instead.t
*
- * On an empty [ModeledShape], this will always return 0.
+ * On an empty [PartitionedMesh], this will always return 0.
*
* Optional argument [otherShapeToThis] contains the transform that maps from [other]'s
- * coordinate space to this [ModeledShape]'s coordinate space, which defaults to the [IDENTITY].
+ * coordinate space to this [PartitionedMesh]'s coordinate space, which defaults to the
+ * [IDENTITY].
*/
@JvmOverloads
+ @FloatRange(from = 0.0, to = 1.0)
public fun coverage(
- other: ModeledShape,
+ other: PartitionedMesh,
otherShapeToThis: AffineTransform = AffineTransform.IDENTITY,
): Float =
ModeledShapeNative.modeledShapeModeledShapeCoverage(
thisShapeNativeAddress = nativeAddress,
otherShapeNativeAddress = other.nativeAddress,
- otherShapeToThisTransformA = otherShapeToThis.a,
- otherShapeToThisTransformB = otherShapeToThis.b,
- otherShapeToThisTransformC = otherShapeToThis.c,
- otherShapeToThisTransformD = otherShapeToThis.d,
- otherShapeToThisTransformE = otherShapeToThis.e,
- otherShapeToThisTransformF = otherShapeToThis.f,
+ otherShapeToThisTransformA = otherShapeToThis.m00,
+ otherShapeToThisTransformB = otherShapeToThis.m10,
+ otherShapeToThisTransformC = otherShapeToThis.m20,
+ otherShapeToThisTransformD = otherShapeToThis.m01,
+ otherShapeToThisTransformE = otherShapeToThis.m11,
+ otherShapeToThisTransformF = otherShapeToThis.m21,
)
/**
- * Returns true if the approximate portion of the [ModeledShape] covered by [triangle] is
+ * Returns true if the approximate portion of the [PartitionedMesh] covered by [triangle] is
* greater than [coverageThreshold].
*
* This is equivalent to:
@@ -324,10 +332,11 @@
*
* but may be faster.
*
- * On an empty [ModeledShape], this will always return 0.
+ * On an empty [PartitionedMesh], this will always return 0.
*
* Optional argument [triangleToThis] contains the transform that maps from [triangle]'s
- * coordinate space to this [ModeledShape]'s coordinate space, which defaults to the [IDENTITY].
+ * coordinate space to this [PartitionedMesh]'s coordinate space, which defaults to the
+ * [IDENTITY].
*/
@JvmOverloads
public fun coverageIsGreaterThan(
@@ -344,16 +353,16 @@
triangleP2X = triangle.p2.x,
triangleP2Y = triangle.p2.y,
coverageThreshold = coverageThreshold,
- triangleToThisTransformA = triangleToThis.a,
- triangleToThisTransformB = triangleToThis.b,
- triangleToThisTransformC = triangleToThis.c,
- triangleToThisTransformD = triangleToThis.d,
- triangleToThisTransformE = triangleToThis.e,
- triangleToThisTransformF = triangleToThis.f,
+ triangleToThisTransformA = triangleToThis.m00,
+ triangleToThisTransformB = triangleToThis.m10,
+ triangleToThisTransformC = triangleToThis.m20,
+ triangleToThisTransformD = triangleToThis.m01,
+ triangleToThisTransformE = triangleToThis.m11,
+ triangleToThisTransformF = triangleToThis.m21,
)
/**
- * Returns true if the approximate portion of the [ModeledShape] covered by [box] is greater
+ * Returns true if the approximate portion of the [PartitionedMesh] covered by [box] is greater
* than [coverageThreshold].
*
* This is equivalent to:
@@ -363,10 +372,10 @@
*
* but may be faster.
*
- * On an empty [ModeledShape], this will always return 0.
+ * On an empty [PartitionedMesh], this will always return 0.
*
* Optional argument [boxToThis] contains the transform that maps from [box]'s coordinate space
- * to this [ModeledShape]'s coordinate space, which defaults to the [IDENTITY].
+ * to this [PartitionedMesh]'s coordinate space, which defaults to the [IDENTITY].
*/
@JvmOverloads
public fun coverageIsGreaterThan(
@@ -381,17 +390,17 @@
boxXMax = box.xMax,
boxYMax = box.yMax,
coverageThreshold = coverageThreshold,
- boxToThisTransformA = boxToThis.a,
- boxToThisTransformB = boxToThis.b,
- boxToThisTransformC = boxToThis.c,
- boxToThisTransformD = boxToThis.d,
- boxToThisTransformE = boxToThis.e,
- boxToThisTransformF = boxToThis.f,
+ boxToThisTransformA = boxToThis.m00,
+ boxToThisTransformB = boxToThis.m10,
+ boxToThisTransformC = boxToThis.m20,
+ boxToThisTransformD = boxToThis.m01,
+ boxToThisTransformE = boxToThis.m11,
+ boxToThisTransformF = boxToThis.m21,
)
/**
- * Returns true if the approximate portion of the [ModeledShape] covered by [parallelogram] is
- * greater than [coverageThreshold].
+ * Returns true if the approximate portion of the [PartitionedMesh] covered by [parallelogram]
+ * is greater than [coverageThreshold].
*
* This is equivalent to:
* ```
@@ -400,11 +409,11 @@
*
* but may be faster.
*
- * On an empty [ModeledShape], this will always return 0.
+ * On an empty [PartitionedMesh], this will always return 0.
*
* Optional argument [parallelogramToThis] contains the transform that maps from
- * [parallelogram]'s coordinate space to this [ModeledShape]'s coordinate space, which defaults
- * to the [IDENTITY].
+ * [parallelogram]'s coordinate space to this [PartitionedMesh]'s coordinate space, which
+ * defaults to the [IDENTITY].
*/
@JvmOverloads
public fun coverageIsGreaterThan(
@@ -421,17 +430,17 @@
parallelogramAngleInRadian = parallelogram.rotation,
parallelogramShearFactor = parallelogram.shearFactor,
coverageThreshold = coverageThreshold,
- parallelogramToThisTransformA = parallelogramToThis.a,
- parallelogramToThisTransformB = parallelogramToThis.b,
- parallelogramToThisTransformC = parallelogramToThis.c,
- parallelogramToThisTransformD = parallelogramToThis.d,
- parallelogramToThisTransformE = parallelogramToThis.e,
- parallelogramToThisTransformF = parallelogramToThis.f,
+ parallelogramToThisTransformA = parallelogramToThis.m00,
+ parallelogramToThisTransformB = parallelogramToThis.m10,
+ parallelogramToThisTransformC = parallelogramToThis.m20,
+ parallelogramToThisTransformD = parallelogramToThis.m01,
+ parallelogramToThisTransformE = parallelogramToThis.m11,
+ parallelogramToThisTransformF = parallelogramToThis.m21,
)
/**
- * Returns true if the approximate portion of this [ModeledShape] covered by the [other]
- * [ModeledShape] is greater than [coverageThreshold].
+ * Returns true if the approximate portion of this [PartitionedMesh] covered by the [other]
+ * [PartitionedMesh] is greater than [coverageThreshold].
*
* This is equivalent to:
* ```
@@ -440,14 +449,15 @@
*
* but may be faster.
*
- * On an empty [ModeledShape], this will always return 0.
+ * On an empty [PartitionedMesh], this will always return 0.
*
* Optional argument [otherShapeToThis] contains the transform that maps from [other]'s
- * coordinate space to this [ModeledShape]'s coordinate space, which defaults to the [IDENTITY].
+ * coordinate space to this [PartitionedMesh]'s coordinate space, which defaults to the
+ * [IDENTITY].
*/
@JvmOverloads
public fun coverageIsGreaterThan(
- other: ModeledShape,
+ other: PartitionedMesh,
coverageThreshold: Float,
otherShapeToThis: AffineTransform = AffineTransform.IDENTITY,
): Boolean =
@@ -455,12 +465,12 @@
thisShapeNativeAddress = nativeAddress,
otherShapeNativeAddress = other.nativeAddress,
coverageThreshold = coverageThreshold,
- otherShapeToThisTransformA = otherShapeToThis.a,
- otherShapeToThisTransformB = otherShapeToThis.b,
- otherShapeToThisTransformC = otherShapeToThis.c,
- otherShapeToThisTransformD = otherShapeToThis.d,
- otherShapeToThisTransformE = otherShapeToThis.e,
- otherShapeToThisTransformF = otherShapeToThis.f,
+ otherShapeToThisTransformA = otherShapeToThis.m00,
+ otherShapeToThisTransformB = otherShapeToThis.m10,
+ otherShapeToThisTransformC = otherShapeToThis.m20,
+ otherShapeToThisTransformD = otherShapeToThis.m01,
+ otherShapeToThisTransformE = otherShapeToThis.m11,
+ otherShapeToThisTransformF = otherShapeToThis.m21,
)
/**
@@ -472,9 +482,15 @@
ModeledShapeNative.initializeSpatialIndex(nativeAddress)
/** Returns true if this MutableEnvelope's spatial index has been initialized. */
- public fun isSpatialIndexInitialized(): Boolean =
+ @VisibleForTesting
+ internal fun isSpatialIndexInitialized(): Boolean =
ModeledShapeNative.isSpatialIndexInitialized(nativeAddress)
+ override fun toString(): String {
+ val address = java.lang.Long.toHexString(nativeAddress)
+ return "PartitionedMesh(bounds=$bounds, meshesByGroup=$meshesByGroup, nativeAddress=$address)"
+ }
+
protected fun finalize() {
// NOMUTANTS--Not tested post garbage collection.
if (nativeAddress == 0L) return
@@ -488,8 +504,8 @@
/**
* Helper object to contain native JNI calls. The alternative to this is putting the methods in
- * [ModeledShape] itself (passes down an unused `jobject`, and doesn't work for native calls used by
- * constructors), or in [ModeledShape.Companion] (makes the `JNI_METHOD` naming less clear).
+ * [PartitionedMesh] itself (passes down an unused `jobject`, and doesn't work for native calls used
+ * by constructors), or in [PartitionedMesh.Companion] (makes the `JNI_METHOD` naming less clear).
*/
private object ModeledShapeNative {
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Point.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Point.kt
deleted file mode 100644
index ce53d31..0000000
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Point.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2024 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.ink.geometry
-
-import androidx.annotation.FloatRange
-import androidx.annotation.RestrictTo
-import kotlin.math.abs
-
-/**
- * Represents a location in 2-dimensional space. See [ImmutablePoint] and [MutablePoint] for
- * concrete classes implementing [Point].
- *
- * The [Point] interface is the read-only view of the underlying data which may or may not be
- * mutable. Use the following concrete classes depending on the application requirement:
- *
- * For the [ImmutablePoint], the underlying data like the [x] and [y] coordinates is set once during
- * construction and does not change afterwards. Use this class for a simple [Point] that is
- * inherently thread-safe because of its immutability. A different value of an immutable object can
- * only be obtained by allocating a new one, and allocations can be expensive due to the risk of
- * garbage collection.
- *
- * For the [MutablePoint], the underlying data might change (e.g. by writing the [x] property). Use
- * this class to hold transient data in a performance critical situation, such as the input or
- * render path --- allocate the underlying [MutablePoint] once, perform operations on it and
- * overwrite it with new data.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public interface Point {
- /** The x-coordinate of the [Point] */
- public val x: Float
-
- /** The y-coordinate of the [Point] */
- public val y: Float
-
- /** Fills the x and y values of [output] with the x and y coordinates of this [Point] */
- public fun getVec(output: MutableVec) {
- output.x = this.x
- output.y = this.y
- }
-
- /**
- * Compares this [Point] with [other], and returns true if the difference between [x] and
- * [other.x] is less than [tolerance], and likewise for [y].
- */
- public fun isAlmostEqual(
- other: Point,
- @FloatRange(from = 0.0) tolerance: Float = 0.0001f,
- ): Boolean = (abs(x - other.x) < tolerance) && (abs(y - other.y) < tolerance)
-
- public companion object {
- /**
- * Adds the x and y values of [lhs] to the x and y values of [rhs] and stores the result in
- * [output].
- */
- @JvmStatic
- public fun add(lhs: Point, rhs: Vec, output: MutablePoint) {
- output.x = lhs.x + rhs.x
- output.y = lhs.y + rhs.y
- }
-
- /**
- * Adds the x and y values of [lhs] to the x and y values of [rhs] and stores the result in
- * [output].
- */
- @JvmStatic
- public fun add(lhs: Vec, rhs: Point, output: MutablePoint) {
- output.x = lhs.x + rhs.x
- output.y = lhs.y + rhs.y
- }
-
- /**
- * Subtracts the x and y values of [rhs] from the x and y values of [lhs] and stores the
- * result in [output].
- */
- @JvmStatic
- public fun subtract(lhs: Point, rhs: Vec, output: MutablePoint) {
- output.x = lhs.x - rhs.x
- output.y = lhs.y - rhs.y
- }
-
- /**
- * Subtracts the x and y values of [rhs] from the x and y values of [lhs] and stores the
- * result in [output].
- */
- @JvmStatic
- public fun subtract(lhs: Point, rhs: Point, output: MutableVec) {
- output.x = lhs.x - rhs.x
- output.y = lhs.y - rhs.y
- }
-
- /**
- * Returns true if [first] and [second] have the same values for all properties of [Point].
- */
- internal fun areEquivalent(first: Point, second: Point): Boolean {
- return first.x == second.x && first.y == second.y
- }
-
- /** Returns a hash code for [point] using its [Point] properties. */
- internal fun hash(point: Point): Int = 31 * point.x.hashCode() + point.y.hashCode()
-
- /** Returns a string representation for [point] using its [Point] properties. */
- internal fun string(point: Point): String = "Point(x=${point.x}, y=${point.y})"
- }
-}
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Segment.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Segment.kt
index fa6d373..6f79eed 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Segment.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Segment.kt
@@ -21,53 +21,68 @@
import kotlin.math.hypot
/** Represents a directed line segment between two points. */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public interface Segment {
- public val start: Vec
- public val end: Vec
+public abstract class Segment internal constructor() {
+ public abstract val start: Vec
+ public abstract val end: Vec
/** The length of the [Segment]. */
- public val length: Float
- @FloatRange(from = 0.0) get() = hypot(start.x - end.x, start.y - end.y)
+ @FloatRange(from = 0.0)
+ public fun computeLength(): Float = hypot(start.x - end.x, start.y - end.y)
/**
* Returns an ImmutableVec with the displacement from start to end. This is equivalent to
- * subtract(end, start, output).
+ * `subtract(end, start, output)`.
+ *
+ * For performance-sensitive code, prefer to use [computeDisplacement] with a pre-allocated
+ * instance of [MutableVec].
*/
- public val vec: ImmutableVec
+ public fun computeDisplacement(): ImmutableVec = ImmutableVec(end.x - start.x, end.y - start.y)
/**
- * Populates [output] with the displacement from start to end. This is equivalent to
- * subtract(end, start, output).
+ * Populates [outVec] with the displacement from start to end. This is equivalent to
+ * `subtract(end, start, output)`. Returns [outVec].
*/
- public fun populateVec(output: MutableVec) {
- output.x = end.x - start.x
- output.y = end.y - start.y
+ public fun computeDisplacement(outVec: MutableVec): MutableVec {
+ outVec.x = end.x - start.x
+ outVec.y = end.y - start.y
+ return outVec
}
- /** Returns an [ImmutableVec] that lies halfway along the segment. */
- public val midpoint: ImmutableVec
+ /**
+ * Returns an [ImmutableVec] that lies halfway along the segment.
+ *
+ * For performance-sensitive code, prefer to use [computeMidpoint] with a pre-allocated instance
+ * of [MutableVec].
+ */
+ public fun computeMidpoint(): ImmutableVec =
+ ImmutableVec((start.x + end.x) / 2, (start.y + end.y) / 2)
- /** Populates [output] with the point halfway along the segment. */
- public fun populateMidpoint(output: MutableVec) {
- output.x = (start.x + end.x) / 2
- output.y = (start.y + end.y) / 2
+ /** Populates [outVec] with the point halfway along the segment. */
+ public fun computeMidpoint(outVec: MutableVec): MutableVec {
+ outVec.x = (start.x + end.x) / 2
+ outVec.y = (start.y + end.y) / 2
+ return outVec
}
- /** Returns the minimum bounding box containing the [Segment]. */
- public val boundingBox: ImmutableBox
- get() = run {
- // TODO(b/354236964): Optimize unnecessary allocations
- val (minX, maxX, minY, maxY) = getBoundingXYCoordinates(this)
- ImmutableBox.fromTwoPoints(ImmutablePoint(minX, minY), ImmutablePoint(maxX, maxY))
- }
-
- /** Populates [output] with the minimum bounding box containing the [Segment]. */
- public fun populateBoundingBox(output: MutableBox) {
+ /**
+ * Returns the minimum bounding box containing the [Segment].
+ *
+ * For performance-sensitive code, prefer to use [computeBoundingBox] with a pre-allocated
+ * instance of [MutableBox].
+ */
+ public fun computeBoundingBox(): ImmutableBox {
// TODO(b/354236964): Optimize unnecessary allocations
val (minX, maxX, minY, maxY) = getBoundingXYCoordinates(this)
- output.setXBounds(minX, maxX)
- output.setYBounds(minY, maxY)
+ return ImmutableBox.fromTwoPoints(ImmutableVec(minX, minY), ImmutableVec(maxX, maxY))
+ }
+
+ /** Populates [outBox] with the minimum bounding box containing the [Segment]. */
+ public fun computeBoundingBox(outBox: MutableBox): MutableBox {
+ // TODO(b/354236964): Optimize unnecessary allocations
+ val (minX, maxX, minY, maxY) = getBoundingXYCoordinates(this)
+ outBox.setXBounds(minX, maxX)
+ outBox.setYBounds(minY, maxY)
+ return outBox
}
/**
@@ -75,30 +90,34 @@
* the start point. You may also think of this as linearly interpolating from the start of the
* segment to the end. Values outside the interval [0, 1] will be extrapolated along the
* infinite line passing through this segment. This is the inverse of [project].
+ *
+ * For performance-sensitive code, prefer to use [computeLerpPoint] with a pre-allocated
+ * instance of [MutableVec].
*/
- public fun lerpPoint(ratio: Float): ImmutableVec =
+ public fun computeLerpPoint(ratio: Float): ImmutableVec =
ImmutableVec(
(1.0f - ratio) * start.x + ratio * end.x,
(1.0f - ratio) * start.y + ratio * end.y
)
/**
- * Fills [output] with the point on the segment at the given ratio of the segment's length,
+ * Fills [outVec] with the point on the segment at the given ratio of the segment's length,
* measured from the start point. You may also think of this as linearly interpolating from the
* start of the segment to the end. Values outside the interval [0, 1] will be extrapolated
* along the infinite line passing through this segment. This is the inverse of [project].
*/
- public fun populateLerpPoint(ratio: Float, output: MutableVec) {
- output.x = (1.0f - ratio) * start.x + ratio * end.x
- output.y = (1.0f - ratio) * start.y + ratio * end.y
+ public fun computeLerpPoint(ratio: Float, outVec: MutableVec): MutableVec {
+ outVec.x = (1.0f - ratio) * start.x + ratio * end.x
+ outVec.y = (1.0f - ratio) * start.y + ratio * end.y
+ return outVec
}
/**
* Returns the multiple of the segment's length at which the infinite extrapolation of this
- * segment is closest to [pointToProject]. This is the inverse of [populateLerpPoint]. If the
- * [length] of this segment is zero, then the projection is undefined and this will throw an
- * error. Note that the [length] may be zero even if [start] and [end] are not equal, if they
- * are sufficiently close that floating-point underflow occurs.
+ * segment is closest to [pointToProject]. This is the inverse of [computeLerpPoint]. If the
+ * [computeLength] of this segment is zero, then the projection is undefined and this will throw
+ * an error. Note that the [computeLength] may be zero even if [start] and [end] are not equal,
+ * if they are sufficiently close that floating-point underflow occurs.
*/
public fun project(pointToProject: Vec): Float {
// TODO(b/354236964): Optimize unnecessary allocations
@@ -108,31 +127,20 @@
// Sometimes start is not exactly equal to the end, but close enough that the
// magnitude-squared still is not positive due to floating-point
// loss-of-precision.
- val magnitudeSquared = vec.magnitudeSquared
+ val magnitudeSquared = computeDisplacement().computeMagnitudeSquared()
if (magnitudeSquared <= 0) {
throw IllegalArgumentException("Projecting onto a segment of zero length is undefined.")
}
val temp = MutableVec()
Vec.subtract(pointToProject, start, temp)
- return Vec.dotProduct(temp, vec) / magnitudeSquared
+ return Vec.dotProduct(temp, computeDisplacement()) / magnitudeSquared
}
/**
* Returns an immutable copy of this object. This will return itself if called on an immutable
* instance.
*/
- public fun asImmutable(): ImmutableSegment
-
- /**
- * Returns an [ImmutableSegment] with some or all of its values taken from `this`. For each
- * value, the returned [ImmutableSegment] will use the given value; if no value is given, it
- * will instead be set to the value on `this`. If `this` is an [ImmutableSegment], and the
- * result would be an identical [ImmutableSegment], then `this` is returned. This occurs when
- * either no values are given, or when all given values are structurally equal to the values in
- * `this`.
- */
- @JvmSynthetic
- public fun asImmutable(start: Vec = this.start, end: Vec = this.end): ImmutableSegment
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public abstract fun asImmutable(): ImmutableSegment
/**
* Compares this [Segment] with [other], and returns true if both [start] points are considered
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Triangle.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Triangle.kt
index ee975b2..abf59f2 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Triangle.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Triangle.kt
@@ -22,47 +22,60 @@
import androidx.ink.nativeloader.NativeLoader
/**
- * A triangle defined by its three corners [p0], [p1] and [p2] in order. This is a read-only
- * interface that has mutable and immutable implementations. See [MutableTriangle] and
- * [ImmutableTriangle].
+ * A triangle defined by its three corners [p0], [p1] and [p2]. The order of these points matter - a
+ * triangle with [p0, p1, p2] is not the same as the permuted [p1, p0, p2], or even the rotated
+ * [p2, p0, p1].
+ *
+ * A [Triangle] may be degenerate, meaning it is constructed with its 3 points colinear. One way
+ * that a [Triangle] may be degenerate is if two or three of its points are at the same location
+ * (coincident).
+ *
+ * This is a read-only interface that has mutable and immutable implementations. See
+ * [MutableTriangle] and [ImmutableTriangle].
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public interface Triangle {
+public abstract class Triangle internal constructor() {
- /** The three points that define the [Triangle]. */
- public val p0: Vec
- public val p1: Vec
- public val p2: Vec
+ /** One of the three points that define the [Triangle]. */
+ public abstract val p0: Vec
+
+ /** One of the three points that define the [Triangle]. */
+ public abstract val p1: Vec
+
+ /** One of the three points that define the [Triangle]. */
+ public abstract val p2: Vec
/**
- * The signed area of the Triangle. If the Triangle's points wind in a positive direction (as
- * defined by [Angle]), then the Triangle's area will be positive. Otherwise, it will be
+ * Return the signed area of the [Triangle]. If the [Triangle] is degenerate, meaning its 3
+ * points are all colinear, then the result will be zero. If its points wind in a positive
+ * direction (as defined by [Angle]), then the result will be positive. Otherwise, it will be
* negative.
*/
- public val signedArea: Float
- get() = run {
- // TODO(b/354236964): Optimize unnecessary allocations
- val p1MinusP0 = MutableVec()
- val p2MinusP1 = MutableVec()
- Vec.subtract(p1, p0, p1MinusP0)
- Vec.subtract(p2, p1, p2MinusP1)
- return 0.5f * Vec.determinant(p1MinusP0, p2MinusP1)
- }
+ public fun computeSignedArea(): Float {
+ // TODO(b/354236964): Optimize unnecessary allocations
+ val p1MinusP0 = MutableVec()
+ val p2MinusP1 = MutableVec()
+ Vec.subtract(p1, p0, p1MinusP0)
+ Vec.subtract(p2, p1, p2MinusP1)
+ return 0.5f * Vec.determinant(p1MinusP0, p2MinusP1)
+ }
/** Returns the minimum bounding box containing the [Triangle]. */
- public val boundingBox: ImmutableBox
- get() = run {
- // TODO(b/354236964): Optimize unnecessary allocations
- val (minX, maxX, minY, maxY) = getBoundingXYCoordinates(this)
- ImmutableBox.fromTwoPoints(ImmutablePoint(minX, minY), ImmutablePoint(maxX, maxY))
- }
-
- /** Populates [output] with the minimum bounding box containing the [Triangle]. */
- public fun populateBoundingBox(output: MutableBox) {
+ public fun computeBoundingBox(): ImmutableBox {
// TODO(b/354236964): Optimize unnecessary allocations
val (minX, maxX, minY, maxY) = getBoundingXYCoordinates(this)
- output.setXBounds(minX, maxX)
- output.setYBounds(minY, maxY)
+ return ImmutableBox.fromTwoPoints(ImmutableVec(minX, minY), ImmutableVec(maxX, maxY))
+ }
+
+ /**
+ * Populates [outBox] with the minimum bounding box containing the [Triangle] and returns
+ * [outBox].
+ */
+ public fun computeBoundingBox(outBox: MutableBox): MutableBox {
+ // TODO(b/354236964): Optimize unnecessary allocations
+ val (minX, maxX, minY, maxY) = getBoundingXYCoordinates(this)
+ outBox.setXBounds(minX, maxX)
+ outBox.setYBounds(minY, maxY)
+ return outBox
}
/**
@@ -85,7 +98,7 @@
* Returns the segment of the Triangle between the point at [index] and the point at [index] + 1
* modulo 3.
*/
- public fun edge(@IntRange(from = 0, to = 2) index: Int): ImmutableSegment {
+ public fun computeEdge(@IntRange(from = 0, to = 2) index: Int): ImmutableSegment {
val modIndex = index % 3
return when (modIndex) {
0 -> ImmutableSegment(p0, p1)
@@ -96,48 +109,41 @@
}
/**
- * Fills [output] with the segment of the Triangle between the point at [index] and the point at
- * [index] + 1 modulo 3.
+ * Fills [outSegment] with the segment of the Triangle between the point at [index] and the
+ * point at [index] + 1 modulo 3. Returns [outSegment].
*/
- public fun populateEdge(@IntRange(from = 0, to = 2) index: Int, output: MutableSegment) {
+ public fun computeEdge(
+ @IntRange(from = 0, to = 2) index: Int,
+ outSegment: MutableSegment,
+ ): MutableSegment {
val modIndex = index % 3
+ val start: Vec
+ val end: Vec
when (modIndex) {
0 -> {
- output.start(p0)
- output.end(p1)
+ start = p0
+ end = p1
}
1 -> {
- output.start(p1)
- output.end(p2)
+ start = p1
+ end = p2
}
2 -> {
- output.start(p2)
- output.end(p0)
+ start = p2
+ end = p0
}
else -> throw IllegalArgumentException("Invalid index: $index")
}
+ outSegment.start.populateFrom(start)
+ outSegment.end.populateFrom(end)
+ return outSegment
}
/**
- * Returns an immutable copy of [this] object. This will return itself if called on an immutable
+ * Returns an immutable copy of this object. This will return itself if called on an immutable
* instance.
*/
- public fun asImmutable(): ImmutableTriangle
-
- /**
- * Returns an [ImmutableTriangle] with some or all of its values taken from `this`. For each
- * value, the returned [ImmutableTriangle] will use the given value; if no value is given, it
- * will instead be set to the value on `this`. If `this` is an [ImmutableTriangle], and the
- * result would be an identical [ImmutableTriangle], then `this` is returned. This occurs when
- * either no values are given, or when all given values are structurally equal to the values in
- * `this`.
- */
- @JvmSynthetic
- public fun asImmutable(
- p0: Vec = this.p0,
- p1: Vec = this.p1,
- p2: Vec = this.p2
- ): ImmutableTriangle
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public abstract fun asImmutable(): ImmutableTriangle
public fun isAlmostEqual(other: Triangle, @FloatRange(from = 0.0) tolerance: Float): Boolean =
p0.isAlmostEqual(other.p0, tolerance) &&
@@ -186,17 +192,14 @@
}
}
-/** Helper object to contain native JNI calls */
+/** Helper object to contain native JNI calls. */
private object TriangleNative {
init {
NativeLoader.load()
}
- /**
- * Helper method to construct a native C++ [Triangle] and [Point], check if the native
- * [Triangle] contains the native [Point], and return the result.
- */
+ /** Helper method to check if a native `ink::Triangle` contains the native `ink::Point`. */
// TODO: b/355248266 - @Keep must go in Proguard config file instead.
external fun nativeContains(
triangleP0X: Float,
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Vec.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Vec.kt
index c02d918..f918578 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Vec.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Vec.kt
@@ -20,88 +20,105 @@
import androidx.annotation.RestrictTo
import kotlin.math.abs
import kotlin.math.atan2
+import kotlin.math.hypot
/**
- * A 2-dimensional vector, representing an offset in space. See [MutableVec] for a mutable, and
- * [ImmutableVec] for an immutable implementation of [Vec]. See [Point] (and its concrete
- * implementations [ImmutablePoint] and [MutablePoint]) for a location in space.
+ * A two-dimensional vector, i.e. an (x, y) coordinate pair. It can be used to represent either:
+ * 1) A two-dimensional offset, i.e. the difference between two points
+ * 2) A point in space, i.e. treating the vector as an offset from the origin
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-public interface Vec {
+public abstract class Vec internal constructor() {
/** The [Vec]'s offset in the x-direction */
- public val x: Float
+ public abstract val x: Float
/** The [Vec]'s offset in the y-direction */
- public val y: Float
+ public abstract val y: Float
/** The length of the [Vec]. */
- public val magnitude: Float
+ @FloatRange(from = 0.0) public fun computeMagnitude(): Float = hypot(x, y)
/** The squared length of the [Vec]. */
- public val magnitudeSquared: Float
+ @FloatRange(from = 0.0) public fun computeMagnitudeSquared(): Float = x * x + y * y
/**
* The direction of the vec, represented as the angle between the positive x-axis and this vec.
- * The [direction] value will lie in the interval [-π, π], and will have the same sign as the
- * vec's y-component.
+ * If either component of the vector is NaN, this returns a NaN angle; otherwise, the returned
+ * value will lie in the interval [-π, π], and will have the same sign as the vector's
+ * y-component.
+ *
+ * Following the behavior of `atan2`, this will return either ±0 or ±π for the zero vector,
+ * depending on the signs of the zeros.
*/
- public val direction: Float
- @FloatRange(from = -Math.PI, to = Math.PI) @AngleRadiansFloat get() = atan2(y, x)
-
- /** Returns a vector with the same direction as this one, but with a magnitude of 1. */
- public val unitVec: ImmutableVec
- get() = VecNative.unitVec(this.x, this.y, ImmutableVec::class.java)
+ @FloatRange(from = -Math.PI, to = Math.PI)
+ @AngleRadiansFloat
+ public fun computeDirection(): Float = atan2(y, x)
/**
- * Modifies [output] into a vector with the same direction as this one, but with a magnitude
- * of 1.
+ * Returns a newly allocated vector with the same direction as this one, but with a magnitude of
+ * `1`. This is equivalent to (but faster than) calling [ImmutableVec.fromDirectionAndMagnitude]
+ * with [computeDirection] and `1`.
+ *
+ * In keeping with the above equivalence, this will return <±1, ±0> for the zero vector,
+ * depending on the signs of the zeros.
+ *
+ * For performance-sensitive code, use [computeUnitVec] with a pre-allocated instance of
+ * [MutableVec].
*/
- public fun populateUnitVec(output: MutableVec) {
- VecNative.populateUnitVec(x, y, output)
+ public fun computeUnitVec(): ImmutableVec =
+ VecNative.unitVec(this.x, this.y, ImmutableVec::class.java)
+
+ /**
+ * Modifies [outVec] into a vector with the same direction as this one, but with a magnitude of
+ * `1`. Returns [outVec]. This is equivalent to (but faster than) calling
+ * [MutableVec.fromDirectionAndMagnitude] with [computeDirection] and `1`.
+ *
+ * In keeping with the above equivalence, this will return <±1, ±0> for the zero vector,
+ * depending on the signs of the zeros.
+ */
+ public fun computeUnitVec(outVec: MutableVec): MutableVec {
+ VecNative.populateUnitVec(x, y, outVec)
+ return outVec
}
/**
- * Returns a vector with the same magnitude as this one, but rotated by (positive) 90 degrees.
+ * Returns a newly allocated vector with the same magnitude as this one, but rotated by
+ * (positive) 90 degrees. For performance-sensitive code, use [computeOrthogonal] with a
+ * pre-allocated instance of [MutableVec].
*/
- public val orthogonal: ImmutableVec
- get() = ImmutableVec(-y, x)
+ public fun computeOrthogonal(): ImmutableVec = ImmutableVec(-y, x)
/**
- * Modifies [output] into a vector with the same magnitude as this one, but rotated by
- * (positive) 90 degrees.
+ * Modifies [outVec] into a vector with the same magnitude as this one, but rotated by
+ * (positive) 90 degrees. Returns [outVec].
*/
- public fun populateOrthogonal(output: MutableVec) {
- output.x = -y
- output.y = x
+ public fun computeOrthogonal(outVec: MutableVec): MutableVec {
+ outVec.x = -y
+ outVec.y = x
+ return outVec
}
- /** Returns a vector with the same magnitude, but pointing in the opposite direction. */
- public val negation: ImmutableVec
- get() = ImmutableVec(-x, -y)
+ /**
+ * Returns a newly allocated vector with the same magnitude, but pointing in the opposite
+ * direction. For performance-sensitive code, use [computeNegation] with a pre-allocated
+ * instance of [MutableVec].
+ */
+ public fun computeNegation(): ImmutableVec = ImmutableVec(-x, -y)
/**
- * Modifies [output] into a vector with the same magnitude, but pointing in the opposite
- * direction.
+ * Modifies [outVec] into a vector with the same magnitude, but pointing in the opposite
+ * direction. Returns [outVec].
*/
- public fun populateNegation(output: MutableVec) {
- output.x = -x
- output.y = -y
+ public fun computeNegation(outVec: MutableVec): MutableVec {
+ outVec.x = -x
+ outVec.y = -y
+ return outVec
}
/**
* Returns an immutable copy of this object. This will return itself if called on an immutable
* instance.
*/
- public val asImmutable: ImmutableVec
-
- /**
- * Returns an [ImmutableVec] with some or all of its values taken from `this`. For each value,
- * the returned [ImmutableVec] will use the given value; if no value is given, it will instead
- * be set to the value on `this`. If `this` is an [ImmutableVec], and the result would be an
- * identical [ImmutableVec], then `this` is returned. This occurs when either no values are
- * given, or when all given values are structurally equal to the values in `this`.
- */
- @JvmSynthetic public fun asImmutable(x: Float = this.x, y: Float = this.y): ImmutableVec
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public abstract fun asImmutable(): ImmutableVec
/**
* Returns true if the angle formed by `this` and [other] is within [angleTolerance] of 0
@@ -131,6 +148,7 @@
* Compares this [Vec] with [other], and returns true if the difference between [x] and
* [other.x] is less than [tolerance], and likewise for [y].
*/
+ @JvmOverloads
public fun isAlmostEqual(
other: Vec,
@FloatRange(from = 0.0) tolerance: Float = 0.0001f,
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/BoxAccumulatorTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/BoxAccumulatorTest.kt
index 6751546..fd7ee5b 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/BoxAccumulatorTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/BoxAccumulatorTest.kt
@@ -77,7 +77,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(0.1F, 2F), ImmutablePoint(3F, 4F))
+ .populateFromTwoPoints(ImmutableVec(0.1F, 2F), ImmutableVec(3F, 4F))
)
)
}
@@ -93,7 +93,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 1.2F), ImmutablePoint(3F, 4F))
+ .populateFromTwoPoints(ImmutableVec(1F, 1.2F), ImmutableVec(3F, 4F))
)
)
}
@@ -109,7 +109,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3.1F, 4F))
+ .populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3.1F, 4F))
)
)
}
@@ -125,7 +125,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4.2F))
+ .populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4.2F))
)
)
}
@@ -143,7 +143,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 1F), ImmutablePoint(4F, 4F))
+ .populateFromTwoPoints(ImmutableVec(1F, 1F), ImmutableVec(4F, 4F))
)
)
}
@@ -172,7 +172,7 @@
envelope.add(
BoxAccumulator(
- ImmutableBox.fromTwoPoints(ImmutablePoint(1.1F, 2.1F), ImmutablePoint(2.9F, 3.9F))
+ ImmutableBox.fromTwoPoints(ImmutableVec(1.1F, 2.1F), ImmutableVec(2.9F, 3.9F))
)
)
@@ -185,7 +185,7 @@
envelope.add(
BoxAccumulator(
- ImmutableBox.fromTwoPoints(ImmutablePoint(0.9F, 1.9F), ImmutablePoint(3.1F, 4.1F))
+ ImmutableBox.fromTwoPoints(ImmutableVec(0.9F, 1.9F), ImmutableVec(3.1F, 4.1F))
)
)
@@ -194,9 +194,9 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(
- ImmutablePoint(0.9F, 1.9F),
- ImmutablePoint(3.1F, 4.1F)
+ .populateFromTwoPoints(
+ ImmutableVec(0.9F, 1.9F),
+ ImmutableVec(3.1F, 4.1F)
)
)
)
@@ -206,12 +206,10 @@
fun addEnvelope_whenNewAndCurrentOverlap_shouldUpdateToUnion() {
val envelope =
BoxAccumulator()
- .add(MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 8F), ImmutablePoint(4F, 9F)))
+ .add(MutableBox().populateFromTwoPoints(ImmutableVec(1F, 8F), ImmutableVec(4F, 9F)))
envelope.add(
- BoxAccumulator(
- ImmutableBox.fromTwoPoints(ImmutablePoint(2F, 7F), ImmutablePoint(3F, 10F))
- )
+ BoxAccumulator(ImmutableBox.fromTwoPoints(ImmutableVec(2F, 7F), ImmutableVec(3F, 10F)))
)
assertThat(envelope)
@@ -219,7 +217,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 7F), ImmutablePoint(4F, 10F))
+ .populateFromTwoPoints(ImmutableVec(1F, 7F), ImmutableVec(4F, 10F))
)
)
}
@@ -229,9 +227,7 @@
val envelope = BoxAccumulator().add(rect1234)
envelope.add(
- BoxAccumulator(
- ImmutableBox.fromTwoPoints(ImmutablePoint(2F, 0F), ImmutablePoint(5F, 1F))
- )
+ BoxAccumulator(ImmutableBox.fromTwoPoints(ImmutableVec(2F, 0F), ImmutableVec(5F, 1F)))
)
assertThat(envelope)
@@ -239,7 +235,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 0F), ImmutablePoint(5F, 4F))
+ .populateFromTwoPoints(ImmutableVec(1F, 0F), ImmutableVec(5F, 4F))
)
)
}
@@ -251,7 +247,7 @@
@Test
fun rect_withBounds_returnsBox() {
- val addition = rect1234.newMutable()
+ val addition = MutableBox().populateFrom(rect1234)
val envelope = BoxAccumulator().add(addition)
val rect = envelope.box
@@ -285,7 +281,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 10F), ImmutablePoint(3F, 15F))
+ .populateFromTwoPoints(ImmutableVec(1F, 10F), ImmutableVec(3F, 15F))
)
)
}
@@ -296,7 +292,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(10F, 10F), ImmutablePoint(20F, 25F))
+ .populateFromTwoPoints(ImmutableVec(10F, 10F), ImmutableVec(20F, 25F))
)
val segment = ImmutableSegment(start = ImmutableVec(1f, 10f), end = ImmutableVec(30f, 150f))
@@ -308,7 +304,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 10F), ImmutablePoint(30F, 150F))
+ .populateFromTwoPoints(ImmutableVec(1F, 10F), ImmutableVec(30F, 150F))
)
)
}
@@ -331,7 +327,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 5F), ImmutablePoint(10F, 20F))
+ .populateFromTwoPoints(ImmutableVec(1F, 5F), ImmutableVec(10F, 20F))
)
)
}
@@ -342,7 +338,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(10F, 10F), ImmutablePoint(20F, 25F))
+ .populateFromTwoPoints(ImmutableVec(10F, 10F), ImmutableVec(20F, 25F))
)
val triangle =
ImmutableTriangle(
@@ -359,7 +355,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 5F), ImmutablePoint(20F, 25F))
+ .populateFromTwoPoints(ImmutableVec(1F, 5F), ImmutableVec(20F, 25F))
)
)
}
@@ -367,7 +363,7 @@
@Test
fun add_rectToEmptyEnvelope_updatesEnvelope() {
val envelope = BoxAccumulator()
- val rect = ImmutableBox.fromTwoPoints(ImmutablePoint(1f, 10f), ImmutablePoint(-3f, -20f))
+ val rect = ImmutableBox.fromTwoPoints(ImmutableVec(1f, 10f), ImmutableVec(-3f, -20f))
envelope.add(rect)
@@ -377,7 +373,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(-3F, -20F), ImmutablePoint(1F, 10F))
+ .populateFromTwoPoints(ImmutableVec(-3F, -20F), ImmutableVec(1F, 10F))
)
)
}
@@ -388,10 +384,9 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(10F, 10F), ImmutablePoint(20F, 25F))
+ .populateFromTwoPoints(ImmutableVec(10F, 10F), ImmutableVec(20F, 25F))
)
- val rect =
- ImmutableBox.fromTwoPoints(ImmutablePoint(100f, 200f), ImmutablePoint(300f, 400f))
+ val rect = ImmutableBox.fromTwoPoints(ImmutableVec(100f, 200f), ImmutableVec(300f, 400f))
envelope.add(rect)
@@ -401,7 +396,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(10F, 10F), ImmutablePoint(300F, 400F))
+ .populateFromTwoPoints(ImmutableVec(10F, 10F), ImmutableVec(300F, 400F))
)
)
}
@@ -411,7 +406,7 @@
val envelope = BoxAccumulator()
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(10f, 20f),
+ center = ImmutableVec(10f, 20f),
width = 4f,
height = 6f,
rotation = Angle.ZERO,
@@ -426,7 +421,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(8F, 17F), ImmutablePoint(12F, 23F))
+ .populateFromTwoPoints(ImmutableVec(8F, 17F), ImmutableVec(12F, 23F))
)
)
}
@@ -437,11 +432,11 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(10F, 10F), ImmutablePoint(20F, 25F))
+ .populateFromTwoPoints(ImmutableVec(10F, 10F), ImmutableVec(20F, 25F))
)
val parallelogram =
ImmutableParallelogram.fromCenterAndDimensions(
- center = ImmutablePoint(100f, 200f),
+ center = ImmutableVec(100f, 200f),
width = 500f,
height = 1000f,
)
@@ -454,9 +449,9 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(
- ImmutablePoint(-150F, -300F),
- ImmutablePoint(350F, 700F)
+ .populateFromTwoPoints(
+ ImmutableVec(-150F, -300F),
+ ImmutableVec(350F, 700F)
)
)
)
@@ -479,13 +474,13 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(10F, 10F), ImmutablePoint(20F, 25F))
+ .populateFromTwoPoints(ImmutableVec(10F, 10F), ImmutableVec(20F, 25F))
)
val secondEnvelope =
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(-150F, -300F), ImmutablePoint(350F, 700F))
+ .populateFromTwoPoints(ImmutableVec(-150F, -300F), ImmutableVec(350F, 700F))
)
envelope.add(secondEnvelope)
@@ -496,9 +491,9 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(
- ImmutablePoint(-150F, -300F),
- ImmutablePoint(350F, 700F)
+ .populateFromTwoPoints(
+ ImmutableVec(-150F, -300F),
+ ImmutableVec(350F, 700F)
)
)
)
@@ -517,7 +512,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 10F), ImmutablePoint(1F, 10F))
+ .populateFromTwoPoints(ImmutableVec(1F, 10F), ImmutableVec(1F, 10F))
)
)
}
@@ -528,7 +523,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(10F, 10F), ImmutablePoint(20F, 25F))
+ .populateFromTwoPoints(ImmutableVec(10F, 10F), ImmutableVec(20F, 25F))
)
val point = MutableVec(1f, 5f)
@@ -540,7 +535,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(1F, 5F), ImmutablePoint(20F, 25F))
+ .populateFromTwoPoints(ImmutableVec(1F, 5F), ImmutableVec(20F, 25F))
)
)
}
@@ -548,7 +543,7 @@
@Test
fun add_emptyMeshToEmptyEnvelope_doesNotUpdateEnvelope() {
val envelope = BoxAccumulator()
- val mesh = ModeledShape()
+ val mesh = PartitionedMesh()
envelope.add(mesh)
@@ -580,8 +575,8 @@
BoxAccumulator()
.add(
ImmutableBox.fromTwoPoints(
- ImmutablePoint(1.00001F, 2.00001F),
- ImmutablePoint(2.99999F, 3.99999F),
+ ImmutableVec(1.00001F, 2.00001F),
+ ImmutableVec(2.99999F, 3.99999F),
)
)
@@ -635,7 +630,7 @@
BoxAccumulator()
.add(
MutableBox()
- .fillFromTwoPoints(ImmutablePoint(2F, 2F), ImmutablePoint(3F, 4F))
+ .populateFromTwoPoints(ImmutableVec(2F, 2F), ImmutableVec(3F, 4F))
)
)
}
@@ -669,8 +664,6 @@
assertThat(string).contains("MutableBox")
}
- private val rect1234 =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
- private val rect5678 =
- ImmutableBox.fromTwoPoints(ImmutablePoint(5F, 6F), ImmutablePoint(7F, 8F))
+ private val rect1234 = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
+ private val rect5678 = ImmutableBox.fromTwoPoints(ImmutableVec(5F, 6F), ImmutableVec(7F, 8F))
}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/BoxTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/BoxTest.kt
index a6ba94b..74fc31d 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/BoxTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/BoxTest.kt
@@ -26,12 +26,12 @@
@Test
fun isAlmostEqual_withToleranceGiven_returnsCorrectValue() {
- val box = ImmutableBox.fromTwoPoints(ImmutablePoint(1f, 2f), ImmutablePoint(3f, 4f))
+ val box = ImmutableBox.fromTwoPoints(ImmutableVec(1f, 2f), ImmutableVec(3f, 4f))
assertThat(box.isAlmostEqual(box, tolerance = 0.00000001f)).isTrue()
assertThat(
box.isAlmostEqual(
- ImmutableBox.fromTwoPoints(ImmutablePoint(1f, 2f), ImmutablePoint(3f, 4f)),
+ ImmutableBox.fromTwoPoints(ImmutableVec(1f, 2f), ImmutableVec(3f, 4f)),
tolerance = 0.00000001f,
)
)
@@ -39,8 +39,8 @@
assertThat(
box.isAlmostEqual(
ImmutableBox.fromTwoPoints(
- ImmutablePoint(1.00001f, 1.99999f),
- ImmutablePoint(3f, 4f)
+ ImmutableVec(1.00001f, 1.99999f),
+ ImmutableVec(3f, 4f)
),
tolerance = 0.000001f,
)
@@ -49,8 +49,8 @@
assertThat(
box.isAlmostEqual(
ImmutableBox.fromTwoPoints(
- ImmutablePoint(1f, 2f),
- ImmutablePoint(3.00001f, 3.99999f)
+ ImmutableVec(1f, 2f),
+ ImmutableVec(3.00001f, 3.99999f)
),
tolerance = 0.000001f,
)
@@ -58,14 +58,14 @@
.isFalse()
assertThat(
box.isAlmostEqual(
- ImmutableBox.fromTwoPoints(ImmutablePoint(1f, 1.99f), ImmutablePoint(3f, 4f)),
+ ImmutableBox.fromTwoPoints(ImmutableVec(1f, 1.99f), ImmutableVec(3f, 4f)),
tolerance = 0.02f,
)
)
.isTrue()
assertThat(
box.isAlmostEqual(
- ImmutableBox.fromTwoPoints(ImmutablePoint(1f, 2f), ImmutablePoint(3.01f, 4f)),
+ ImmutableBox.fromTwoPoints(ImmutableVec(1f, 2f), ImmutableVec(3.01f, 4f)),
tolerance = 0.02f,
)
)
@@ -74,11 +74,11 @@
@Test
fun isAlmostEqual_whenSameInterface_returnsTrue() {
- val box = MutableBox().fillFromTwoPoints(ImmutablePoint(1f, 2f), ImmutablePoint(3f, 4f))
+ val box = MutableBox().populateFromTwoPoints(ImmutableVec(1f, 2f), ImmutableVec(3f, 4f))
val other =
ImmutableBox.fromTwoPoints(
- ImmutablePoint(0.99999f, 2.00001f),
- ImmutablePoint(3.00001f, 3.99999f),
+ ImmutableVec(0.99999f, 2.00001f),
+ ImmutableVec(3.00001f, 3.99999f)
)
assertThat(box.isAlmostEqual(other, tolerance = 0.0001f)).isTrue()
}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableAffineTransformTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableAffineTransformTest.kt
index e498235..f41c54f 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableAffineTransformTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableAffineTransformTest.kt
@@ -95,8 +95,8 @@
@Test
fun equals_whenDifferentF_returnsFalse() {
- val affineTransform = ImmutableAffineTransform(A, B, C, D, E, 6f)
- val otherTransform = ImmutableAffineTransform(A, B, C, D, E, 60f)
+ val affineTransform = ImmutableAffineTransform(A, B, C, D, E, F)
+ val otherTransform = ImmutableAffineTransform(A, B, C, D, E, DIFFERENT_F)
assertThat(affineTransform).isNotEqualTo(otherTransform)
}
@@ -104,8 +104,7 @@
@Test
fun translate_returnsCorrectImmutableAffineTransform() {
val translate = ImmutableAffineTransform.translate(ImmutableVec(4.12f, -19.9f))
- val expected =
- ImmutableAffineTransform(a = 1f, b = 0f, c = 4.12f, d = 0f, e = 1f, f = -19.9f)
+ val expected = ImmutableAffineTransform(1f, 0f, 4.12f, 0f, 1f, -19.9f)
assertThat(translate).isEqualTo(expected)
}
@@ -113,7 +112,7 @@
@Test
fun scale_callsJniAndReturnsCorrectValue() {
val scale = ImmutableAffineTransform.scale(2.9f)
- val expected = ImmutableAffineTransform(a = 2.9f, b = 0f, c = 0f, d = 0f, e = 2.9f, f = 0f)
+ val expected = ImmutableAffineTransform(2.9f, 0f, 0f, 0f, 2.9f, 0f)
assertThat(scale).isEqualTo(expected)
}
@@ -121,8 +120,7 @@
@Test
fun scale_withTwoArguments_callsJniAndReturnsCorrectValue() {
val scale = ImmutableAffineTransform.scale(-7.13f, 19.71f)
- val expected =
- ImmutableAffineTransform(a = -7.13f, b = 0f, c = 0f, d = 0f, e = 19.71f, f = 0f)
+ val expected = ImmutableAffineTransform(-7.13f, 0f, 0f, 0f, 19.71f, 0f)
assertThat(scale).isEqualTo(expected)
}
@@ -130,7 +128,7 @@
@Test
fun scaleX_callsJniAndReturnsCorrectValue() {
val scale = ImmutableAffineTransform.scaleX(100.54f)
- val expected = ImmutableAffineTransform(a = 100.54f, b = 0f, c = 0f, d = 0f, e = 1f, f = 0f)
+ val expected = ImmutableAffineTransform(100.54f, 0f, 0f, 0f, 1f, 0f)
assertThat(scale).isEqualTo(expected)
}
@@ -138,7 +136,7 @@
@Test
fun scaleY_callsJniAndReturnsCorrectValue() {
val scale = ImmutableAffineTransform.scaleY(12f)
- val expected = ImmutableAffineTransform(a = 1f, b = 0f, c = 0f, d = 0f, e = 12f, f = 0f)
+ val expected = ImmutableAffineTransform(1f, 0f, 0f, 0f, 12f, 0f)
assertThat(scale).isEqualTo(expected)
}
@@ -148,32 +146,32 @@
val identityTransform = AffineTransform.IDENTITY
val identityOutput = MutableAffineTransform()
- identityTransform.populateInverse(identityOutput)
+ identityTransform.computeInverse(identityOutput)
assertThat(identityOutput).isEqualTo(AffineTransform.IDENTITY)
val scaleTransform = ImmutableAffineTransform.scale(4f, 10f)
val scaleOutput = MutableAffineTransform()
- scaleTransform.populateInverse(scaleOutput)
+ scaleTransform.computeInverse(scaleOutput)
assertThat(scaleOutput).isEqualTo(ImmutableAffineTransform.scale(0.25f, 0.1f))
val translateTransform = ImmutableAffineTransform.translate(ImmutableVec(5f, 10f))
val translateOutput = MutableAffineTransform()
- translateTransform.populateInverse(translateOutput)
+ translateTransform.computeInverse(translateOutput)
assertThat(translateOutput)
.isEqualTo(ImmutableAffineTransform.translate(ImmutableVec(-5f, -10f)))
val shearXTransform = ImmutableAffineTransform(1f, 5F, 0f, 0f, 1f, 0f)
val shearXOutput = MutableAffineTransform()
- shearXTransform.populateInverse(shearXOutput)
+ shearXTransform.computeInverse(shearXOutput)
assertThat(shearXOutput).isEqualTo(ImmutableAffineTransform(1f, -5f, 0f, 0f, 1f, 0f))
val shearYTransform = ImmutableAffineTransform(1f, 0F, 0f, 5f, 1f, 0f)
val shearYOutput = MutableAffineTransform()
- shearYTransform.populateInverse(shearYOutput)
+ shearYTransform.computeInverse(shearYOutput)
assertThat(shearYOutput).isEqualTo(ImmutableAffineTransform(1f, 0f, 0f, -5f, 1f, 0f))
}
@@ -182,7 +180,7 @@
// This is equivalent to ImmutableAffineTransform.scale(4f, 10f)
val testTransform = MutableAffineTransform(4f, 0f, 0f, 0f, 10f, 0f)
- testTransform.populateInverse(testTransform)
+ testTransform.computeInverse(testTransform)
assertThat(testTransform).isEqualTo(ImmutableAffineTransform.scale(0.25f, 0.1f))
}
@@ -191,14 +189,14 @@
val zeroesTransform = MutableAffineTransform(0f, 0f, 0f, 0f, 0f, 0f)
assertFailsWith<IllegalArgumentException> {
- zeroesTransform.populateInverse(zeroesTransform)
+ zeroesTransform.computeInverse(zeroesTransform)
}
// Determinant = a * e - b * d = 2 * 16 - 4 * 8 = 0
val determinantOfZeroTransform = MutableAffineTransform(2f, 4f, 0f, 8f, 16f, 0f)
assertFailsWith<IllegalArgumentException> {
- determinantOfZeroTransform.populateInverse(determinantOfZeroTransform)
+ determinantOfZeroTransform.computeInverse(determinantOfZeroTransform)
}
}
@@ -254,7 +252,7 @@
val identitySegment = MutableSegment()
identityTransform.applyTransform(testSegment, identitySegment)
assertThat(identitySegment)
- .isEqualTo(MutableSegment(ImmutableVec(4F, 6F), ImmutableVec(40F, 60F)))
+ .isEqualTo(MutableSegment(MutableVec(4F, 6F), MutableVec(40F, 60F)))
val translateTransform = ImmutableAffineTransform.translate(ImmutableVec(3F, -20F))
val translateSegment = MutableSegment()
@@ -370,26 +368,26 @@
@Test
fun applyTransform_whenAppliedToABox_correctlyModifiesParallelogram() {
- val testBox = ImmutableBox.fromCenterAndDimensions(ImmutablePoint(4f, 1f), 6f, 8f)
+ val testBox = ImmutableBox.fromCenterAndDimensions(ImmutableVec(4f, 1f), 6f, 8f)
val identityTransform = AffineTransform.IDENTITY
val identityParallelogram = MutableParallelogram()
identityTransform.applyTransform(testBox, identityParallelogram)
assertThat(identityParallelogram)
- .isEqualTo(MutableParallelogram.fromCenterAndDimensions(ImmutablePoint(4f, 1f), 6f, 8f))
+ .isEqualTo(MutableParallelogram.fromCenterAndDimensions(MutableVec(4f, 1f), 6f, 8f))
val translateTransform = ImmutableAffineTransform.translate(ImmutableVec(1F, 3F))
val translateParallelogram = MutableParallelogram()
translateTransform.applyTransform(testBox, translateParallelogram)
assertThat(translateParallelogram)
- .isEqualTo(MutableParallelogram.fromCenterAndDimensions(ImmutablePoint(5f, 4f), 6f, 8f))
+ .isEqualTo(MutableParallelogram.fromCenterAndDimensions(MutableVec(5f, 4f), 6f, 8f))
val scaleBy2ValuesTransform = ImmutableAffineTransform.scale(2.5F, -.5F)
val scaleBy2ValuesParallelogram = MutableParallelogram()
scaleBy2ValuesTransform.applyTransform(testBox, scaleBy2ValuesParallelogram)
assertThat(scaleBy2ValuesParallelogram)
.isEqualTo(
- MutableParallelogram.fromCenterAndDimensions(ImmutablePoint(10f, -0.5f), 15f, -4f)
+ MutableParallelogram.fromCenterAndDimensions(MutableVec(10f, -0.5f), 15f, -4f)
)
val scaleBy1ValueTransform = ImmutableAffineTransform.scale(2.5F)
@@ -397,24 +395,20 @@
scaleBy1ValueTransform.applyTransform(testBox, scaleBy1ValueParallelogram)
assertThat(scaleBy1ValueParallelogram)
.isEqualTo(
- MutableParallelogram.fromCenterAndDimensions(ImmutablePoint(10f, 2.5f), 15f, 20f)
+ MutableParallelogram.fromCenterAndDimensions(MutableVec(10f, 2.5f), 15f, 20f)
)
val scaleXTransform = ImmutableAffineTransform.scaleX(2.5F)
val scaleXParallelogram = MutableParallelogram()
scaleXTransform.applyTransform(testBox, scaleXParallelogram)
assertThat(scaleXParallelogram)
- .isEqualTo(
- MutableParallelogram.fromCenterAndDimensions(ImmutablePoint(10f, 1f), 15f, 8f)
- )
+ .isEqualTo(MutableParallelogram.fromCenterAndDimensions(MutableVec(10f, 1f), 15f, 8f))
val scaleYTransform = ImmutableAffineTransform.scaleY(2.5F)
val scaleYParallelogram = MutableParallelogram()
scaleYTransform.applyTransform(testBox, scaleYParallelogram)
assertThat(scaleYParallelogram)
- .isEqualTo(
- MutableParallelogram.fromCenterAndDimensions(ImmutablePoint(4f, 2.5f), 6f, 20f)
- )
+ .isEqualTo(MutableParallelogram.fromCenterAndDimensions(MutableVec(4f, 2.5f), 6f, 20f))
val shearXTransform = ImmutableAffineTransform(1f, 2.5F, 0f, 0f, 1f, 0f)
val shearXParallelogram = MutableParallelogram()
@@ -422,7 +416,7 @@
assertThat(shearXParallelogram)
.isEqualTo(
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(6.5f, 1f),
+ MutableVec(6.5f, 1f),
6f,
8f,
0.0f,
@@ -439,7 +433,7 @@
Parallelogram.areNear(
rotateParallelogram,
MutableParallelogram.fromCenterDimensionsAndRotation(
- ImmutablePoint(-4f, -1f),
+ MutableVec(-4f, -1f),
6f,
8f,
Angle.HALF_TURN_RADIANS,
@@ -453,7 +447,7 @@
fun applyTransform_whenAppliedToAParallelogram_correctlyModifiesParallelogram() {
val testParallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(4f, 1f),
+ ImmutableVec(4f, 1f),
6f,
8f,
Angle.QUARTER_TURN_RADIANS,
@@ -466,7 +460,7 @@
assertThat(identityParallelogram)
.isEqualTo(
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(4f, 1f),
+ MutableVec(4f, 1f),
6f,
8f,
Angle.QUARTER_TURN_RADIANS,
@@ -480,7 +474,7 @@
assertThat(translateParallelogram)
.isEqualTo(
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(5f, 4f),
+ MutableVec(5f, 4f),
6f,
8f,
Angle.QUARTER_TURN_RADIANS,
@@ -495,7 +489,7 @@
Parallelogram.areNear(
scaleBy2ValuesParallelogram,
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(10f, -0.5f),
+ MutableVec(10f, -0.5f),
3f,
-20f,
Angle.QUARTER_TURN_RADIANS + Angle.HALF_TURN_RADIANS,
@@ -513,7 +507,7 @@
Parallelogram.areNear(
scaleBy1ValueParallelogram,
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(10f, 2.5f),
+ MutableVec(10f, 2.5f),
15f,
20f,
Angle.QUARTER_TURN_RADIANS,
@@ -531,7 +525,7 @@
Parallelogram.areNear(
scaleXParallelogram,
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(10f, 1f),
+ MutableVec(10f, 1f),
6f,
20f,
Angle.QUARTER_TURN_RADIANS,
@@ -549,7 +543,7 @@
Parallelogram.areNear(
scaleYParallelogram,
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(4f, 2.5f),
+ MutableVec(4f, 2.5f),
15f,
8f,
Angle.QUARTER_TURN_RADIANS,
@@ -569,7 +563,7 @@
Parallelogram.areNear(
rotateParallelogram,
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-4f, -1f),
+ MutableVec(-4f, -1f),
6f,
8f,
Angle.HALF_TURN_RADIANS + Angle.QUARTER_TURN_RADIANS,
@@ -584,7 +578,7 @@
fun applyTransform_whenAppliedToAMutableParallelogram_canModifyInputAsOutput() {
val testMutableParallelogram =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(4f, 1f),
+ MutableVec(4f, 1f),
6f,
8f,
Angle.QUARTER_TURN_RADIANS,
@@ -596,7 +590,7 @@
assertThat(testMutableParallelogram)
.isEqualTo(
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(5f, 4f),
+ MutableVec(5f, 4f),
6f,
8f,
Angle.QUARTER_TURN_RADIANS,
@@ -606,6 +600,47 @@
}
@Test
+ fun constructWithValuesAndGetValues_shouldRoundTrip() {
+ val affineTransform = ImmutableAffineTransform(1F, 2F, 3F, 4F, 5F, 6F)
+
+ val outValues = FloatArray(6)
+ affineTransform.getValues(outValues)
+
+ assertThat(outValues)
+ .usingExactEquality()
+ .containsExactly(floatArrayOf(1F, 2F, 3F, 4F, 5F, 6F))
+ }
+
+ @Test
+ fun constructWithArrayAndGetValues_shouldRoundTrip() {
+ val values = floatArrayOf(1F, 2F, 3F, 4F, 5F, 6F)
+ val affineTransform = ImmutableAffineTransform(values)
+
+ val outValues = FloatArray(6)
+ affineTransform.getValues(outValues)
+
+ assertThat(outValues).usingExactEquality().containsExactly(values)
+ }
+
+ @Test
+ fun constructWithValues_shouldMatchConstructedWithFactoryFunctions() {
+ assertThat(ImmutableAffineTransform(7F, 0F, 0F, 0F, 7F, 0F))
+ .isEqualTo(ImmutableAffineTransform.scale(7F))
+
+ assertThat(ImmutableAffineTransform(3F, 0F, 0F, 0F, 5F, 0F))
+ .isEqualTo(ImmutableAffineTransform.scale(3F, 5F))
+
+ assertThat(ImmutableAffineTransform(4F, 0F, 0F, 0F, 1F, 0F))
+ .isEqualTo(ImmutableAffineTransform.scaleX(4F))
+
+ assertThat(ImmutableAffineTransform(1F, 0F, 0F, 0F, 2F, 0F))
+ .isEqualTo(ImmutableAffineTransform.scaleY(2F))
+
+ assertThat(ImmutableAffineTransform(1F, 0F, 8F, 0F, 1F, 9F))
+ .isEqualTo(ImmutableAffineTransform.translate(ImmutableVec(8F, 9F)))
+ }
+
+ @Test
fun asImmutable_returnsSelf() {
val affineTransform = ImmutableAffineTransform(A, B, C, D, E, F)
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableBoxTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableBoxTest.kt
index ccde8423..e05f766 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableBoxTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableBoxTest.kt
@@ -26,7 +26,7 @@
@Test
fun fromCenterAndDimensions_constructsCorrectImmutableBox() {
- val rect = ImmutableBox.fromCenterAndDimensions(ImmutablePoint(20f, -50f), 10f, 20f)
+ val rect = ImmutableBox.fromCenterAndDimensions(ImmutableVec(20f, -50f), 10f, 20f)
assertThat(rect.xMin).isEqualTo(15f)
assertThat(rect.xMax).isEqualTo(25f)
@@ -38,7 +38,7 @@
@Test
fun fromTwoPoints_constructsCorrectImmutableBox() {
- val rect = ImmutableBox.fromTwoPoints(ImmutablePoint(20f, -50f), MutablePoint(-70f, 100f))
+ val rect = ImmutableBox.fromTwoPoints(ImmutableVec(20f, -50f), MutableVec(-70f, 100f))
assertThat(rect.xMin).isEqualTo(-70f)
assertThat(rect.xMax).isEqualTo(20f)
@@ -50,7 +50,7 @@
@Test
fun minMaxFields_whenAllZeroes_allAreZero() {
- val zeroes = ImmutableBox.fromTwoPoints(ImmutablePoint(0F, 0F), ImmutablePoint(0F, 0F))
+ val zeroes = ImmutableBox.fromTwoPoints(ImmutableVec(0F, 0F), ImmutableVec(0F, 0F))
assertThat(zeroes.xMin).isEqualTo(0F)
assertThat(zeroes.yMin).isEqualTo(0F)
assertThat(zeroes.xMax).isEqualTo(0F)
@@ -59,7 +59,7 @@
@Test
fun minMaxFields_whenDeclaredInMinMaxOrder_matchOrder() {
- val inOrder = ImmutableBox.fromTwoPoints(ImmutablePoint(-1F, -2F), ImmutablePoint(3F, 4F))
+ val inOrder = ImmutableBox.fromTwoPoints(ImmutableVec(-1F, -2F), ImmutableVec(3F, 4F))
assertThat(inOrder.xMin).isEqualTo(-1F)
assertThat(inOrder.yMin).isEqualTo(-2F)
assertThat(inOrder.xMax).isEqualTo(3F)
@@ -68,8 +68,7 @@
@Test
fun minMaxFields_whenDeclaredOutOfOrder_doNotMatchOrder() {
- val outOfOrder =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(-3F, -4F))
+ val outOfOrder = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(-3F, -4F))
assertThat(outOfOrder.xMin).isEqualTo(-3F)
assertThat(outOfOrder.yMin).isEqualTo(-4F)
assertThat(outOfOrder.xMax).isEqualTo(1F)
@@ -78,7 +77,7 @@
@Test
fun widthHeight_whenAllZeroes_areAllZero() {
- val zeroes = ImmutableBox.fromTwoPoints(ImmutablePoint(0F, 0F), ImmutablePoint(0F, 0F))
+ val zeroes = ImmutableBox.fromTwoPoints(ImmutableVec(0F, 0F), ImmutableVec(0F, 0F))
assertThat(zeroes.width).isEqualTo(0)
assertThat(zeroes.height).isEqualTo(0)
@@ -86,7 +85,7 @@
@Test
fun widthHeight_whenDeclaredInOrder_areCorrectValues() {
- val inOrder = ImmutableBox.fromTwoPoints(ImmutablePoint(-1F, -2F), ImmutablePoint(3F, 4F))
+ val inOrder = ImmutableBox.fromTwoPoints(ImmutableVec(-1F, -2F), ImmutableVec(3F, 4F))
assertThat(inOrder.width).isEqualTo(4F)
assertThat(inOrder.height).isEqualTo(6F)
@@ -94,8 +93,7 @@
@Test
fun widthHeight_whenDeclaredOutOfOrder_areCorrectValues() {
- val outOfOrder =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(-3F, -4F))
+ val outOfOrder = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(-3F, -4F))
assertThat(outOfOrder.width).isEqualTo(4F)
assertThat(outOfOrder.height).isEqualTo(6F)
@@ -103,8 +101,7 @@
@Test
fun equals_whenSameInstance_returnsTrueAndSameHashCode() {
- val immutableBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val immutableBox = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(immutableBox).isEqualTo(immutableBox)
assertThat(immutableBox.hashCode()).isEqualTo(immutableBox.hashCode())
@@ -112,18 +109,17 @@
@Test
fun equals_whenDifferentType_returnsFalse() {
- val immutableBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val immutableBox = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
- assertThat(immutableBox).isNotEqualTo(ImmutablePoint(1F, 2F))
+ assertThat(immutableBox).isNotEqualTo(ImmutableVec(1F, 2F))
}
@Test
fun equals_whenSameInterfacePropertiesAndDifferentType_returnsTrue() {
- val point1 = ImmutablePoint(1F, 2F)
- val point2 = ImmutablePoint(3F, 4F)
+ val point1 = ImmutableVec(1F, 2F)
+ val point2 = ImmutableVec(3F, 4F)
val immutableBox = ImmutableBox.fromTwoPoints(point1, point2)
- val mutableBox = MutableBox().fillFromTwoPoints(point1, point2)
+ val mutableBox = MutableBox().populateFromTwoPoints(point1, point2)
assertThat(immutableBox).isEqualTo(mutableBox)
assertThat(immutableBox.hashCode()).isEqualTo(mutableBox.hashCode())
@@ -131,9 +127,8 @@
@Test
fun equals_whenSameValues_returnsTrueAndSameHashCode() {
- val immutableBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
- val other = ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val immutableBox = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
+ val other = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(immutableBox).isEqualTo(other)
assertThat(immutableBox.hashCode()).isEqualTo(other.hashCode())
@@ -141,9 +136,8 @@
@Test
fun equals_whenSameValuesOutOfOrder_returnsTrueAndSameHashCode() {
- val immutableBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
- val other = ImmutableBox.fromTwoPoints(ImmutablePoint(3F, 4F), ImmutablePoint(1F, 2F))
+ val immutableBox = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
+ val other = ImmutableBox.fromTwoPoints(ImmutableVec(3F, 4F), ImmutableVec(1F, 2F))
assertThat(immutableBox).isEqualTo(other)
assertThat(immutableBox.hashCode()).isEqualTo(other.hashCode())
@@ -151,103 +145,65 @@
@Test
fun equals_whenDifferentXMin_returnsFalse() {
- val immutableBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val immutableBox = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(immutableBox)
- .isNotEqualTo(
- ImmutableBox.fromTwoPoints(ImmutablePoint(-1F, 2F), ImmutablePoint(3F, 4F))
- )
+ .isNotEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(-1F, 2F), ImmutableVec(3F, 4F)))
}
@Test
fun equals_whenDifferentYMin_returnsFalse() {
- val immutableBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val immutableBox = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(immutableBox)
- .isNotEqualTo(
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, -2F), ImmutablePoint(3F, 4F))
- )
+ .isNotEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(1F, -2F), ImmutableVec(3F, 4F)))
}
@Test
fun equals_whenDifferentXMax_returnsFalse() {
- val immutableBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val immutableBox = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(immutableBox)
- .isNotEqualTo(
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(30F, 4F))
- )
+ .isNotEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(30F, 4F)))
}
@Test
fun equals_whenDifferentYMax_returnsFalse() {
- val immutableBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val immutableBox = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(immutableBox)
- .isNotEqualTo(
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 40F))
- )
+ .isNotEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 40F)))
}
@Test
- fun newMutable_matchesValues() {
- val immutableBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ fun populateCenter_modifiesMutablePoint() {
+ val immutableBox = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 20F), ImmutableVec(3F, 40F))
+ val outCenter = MutableVec()
+ immutableBox.computeCenter(outCenter)
- assertThat(immutableBox.newMutable())
- .isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
- )
+ assertThat(outCenter).isEqualTo(MutableVec(2F, 30F))
}
@Test
- fun fillMutable_correctlyModifiesOutput() {
- val immutableBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
- val output = MutableBox()
+ fun corners_modifiesMutableVecs() {
+ val rect = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 20F), ImmutableVec(3F, 40F))
+ val p0 = MutableVec()
+ val p1 = MutableVec()
+ val p2 = MutableVec()
+ val p3 = MutableVec()
+ rect.computeCorners(p0, p1, p2, p3)
- immutableBox.fillMutable(output)
-
- assertThat(output)
- .isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
- )
- }
-
- @Test
- fun center_modifiesMutablePoint() {
- val immutableBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 20F), ImmutablePoint(3F, 40F))
- val outCenter = MutablePoint()
- immutableBox.center(outCenter)
-
- assertThat(outCenter).isEqualTo(MutablePoint(2F, 30F))
- }
-
- @Test
- fun corners_modifiesMutablePoints() {
- val rect = ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 20F), ImmutablePoint(3F, 40F))
- val p0 = MutablePoint()
- val p1 = MutablePoint()
- val p2 = MutablePoint()
- val p3 = MutablePoint()
- rect.corners(p0, p1, p2, p3)
-
- assertThat(p0).isEqualTo(MutablePoint(1F, 20F))
- assertThat(p1).isEqualTo(MutablePoint(3F, 20F))
- assertThat(p2).isEqualTo(MutablePoint(3F, 40F))
- assertThat(p3).isEqualTo(MutablePoint(1F, 40F))
+ assertThat(p0).isEqualTo(MutableVec(1F, 20F))
+ assertThat(p1).isEqualTo(MutableVec(3F, 20F))
+ assertThat(p2).isEqualTo(MutableVec(3F, 40F))
+ assertThat(p3).isEqualTo(MutableVec(1F, 40F))
}
@Test
fun contains_returnsCorrectValuesWithPoint() {
- val rect = ImmutableBox.fromTwoPoints(ImmutablePoint(10F, 600F), ImmutablePoint(40F, 900F))
- val innerPoint = ImmutablePoint(30F, 700F)
- val outerPoint = ImmutablePoint(70F, 2000F)
+ val rect = ImmutableBox.fromTwoPoints(ImmutableVec(10F, 600F), ImmutableVec(40F, 900F))
+ val innerPoint = ImmutableVec(30F, 700F)
+ val outerPoint = ImmutableVec(70F, 2000F)
assertThat(rect.contains(innerPoint)).isTrue()
assertThat(rect.contains(outerPoint)).isFalse()
@@ -255,83 +211,10 @@
@Test
fun contains_returnsCorrectValuesWithBox() {
- val outerRect =
- ImmutableBox.fromTwoPoints(ImmutablePoint(10F, 600F), ImmutablePoint(40F, 900F))
- val innerRect =
- ImmutableBox.fromTwoPoints(ImmutablePoint(20F, 700F), ImmutablePoint(30F, 800F))
+ val outerRect = ImmutableBox.fromTwoPoints(ImmutableVec(10F, 600F), ImmutableVec(40F, 900F))
+ val innerRect = ImmutableBox.fromTwoPoints(ImmutableVec(20F, 700F), ImmutableVec(30F, 800F))
assertThat(outerRect.contains(innerRect)).isTrue()
assertThat(innerRect.contains(outerRect)).isFalse()
}
-
- @Test
- fun copy_withNoArguments_returnsThis() {
- val original = ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
-
- assertThat(original.copy()).isSameInstanceAs(original)
- }
-
- @Test
- fun copy_withArguments_makesCopy() {
- val x1 = 1F
- val y1 = 2F
- val x2 = 3F
- val y2 = 4F
- val original = ImmutableBox.fromTwoPoints(ImmutablePoint(x1, y1), ImmutablePoint(x2, y2))
- // Different values that won't result in the min/max in either x or y dimension flipping.
- val differentX1 = 0.5F
- val differentY1 = 1.5F
- val differentX2 = 2.5F
- val differentY2 = 3.5F
-
- // Change all values.
- assertThat(original.copy(differentX1, differentY1, differentX2, differentY2))
- .isEqualTo(
- ImmutableBox.fromTwoPoints(
- ImmutablePoint(differentX1, differentY1),
- ImmutablePoint(differentX2, differentY2),
- )
- )
-
- // Change x1.
- assertThat(original.copy(x1 = differentX1))
- .isEqualTo(
- ImmutableBox.fromTwoPoints(ImmutablePoint(differentX1, y1), ImmutablePoint(x2, y2))
- )
-
- // Change y1.
- assertThat(original.copy(y1 = differentY1))
- .isEqualTo(
- ImmutableBox.fromTwoPoints(ImmutablePoint(x1, differentY1), ImmutablePoint(x2, y2))
- )
-
- // Change x2.
- assertThat(original.copy(x2 = differentX2))
- .isEqualTo(
- ImmutableBox.fromTwoPoints(ImmutablePoint(x1, y1), ImmutablePoint(differentX2, y2))
- )
-
- // Change y2.
- assertThat(original.copy(y2 = differentY2))
- .isEqualTo(
- ImmutableBox.fromTwoPoints(ImmutablePoint(x1, y1), ImmutablePoint(x2, differentY2))
- )
- }
-
- @Test
- fun copy_withArgumentsThatReverseBounds_makesCopyWith() {
- val x1 = 1F
- val y1 = 2F
- val x2 = 3F
- val y2 = 4F
- val original = ImmutableBox.fromTwoPoints(ImmutablePoint(x1, y1), ImmutablePoint(x2, y2))
- // Different value that results in the min/max in x dimension flipping.
- val differentX1 = 5F
-
- // Change x1 will flip x1 and x2 values.
- assertThat(original.copy(x1 = differentX1))
- .isEqualTo(
- ImmutableBox.fromTwoPoints(ImmutablePoint(x2, y1), ImmutablePoint(differentX1, y2))
- )
- }
}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableParallelogramTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableParallelogramTest.kt
index c436f4c..b14ff70 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableParallelogramTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableParallelogramTest.kt
@@ -27,9 +27,9 @@
@Test
fun fromCenterAndDimensions_constructsCorrectImmutableParallelogram() {
val parallelogram =
- ImmutableParallelogram.fromCenterAndDimensions(ImmutablePoint(10f, 0f), 6f, 4f)
+ ImmutableParallelogram.fromCenterAndDimensions(ImmutableVec(10f, 0f), 6f, 4f)
- assertThat(parallelogram.center).isEqualTo(ImmutablePoint(10f, 0f))
+ assertThat(parallelogram.center).isEqualTo(ImmutableVec(10f, 0f))
assertThat(parallelogram.width).isEqualTo(6f)
assertThat(parallelogram.height).isEqualTo(4f)
assertThat(parallelogram.rotation).isZero()
@@ -40,13 +40,13 @@
fun fromCenterDimensionsAndRotation_constructsCorrectImmutableParallelogram() {
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsAndRotation(
- ImmutablePoint(10f, 0f),
+ ImmutableVec(10f, 0f),
6f,
4f,
Angle.FULL_TURN_RADIANS,
)
- assertThat(parallelogram.center).isEqualTo(ImmutablePoint(10f, 0f))
+ assertThat(parallelogram.center).isEqualTo(ImmutableVec(10f, 0f))
assertThat(parallelogram.width).isEqualTo(6f)
assertThat(parallelogram.height).isEqualTo(4f)
assertThat(parallelogram.rotation).isZero()
@@ -57,14 +57,14 @@
fun fromCenterDimensionsRotationAndShear_constructsCorrectImmutableParallelogram() {
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(10f, 0f),
+ ImmutableVec(10f, 0f),
6f,
4f,
Angle.HALF_TURN_RADIANS,
1f,
)
- assertThat(parallelogram.center).isEqualTo(ImmutablePoint(10f, 0f))
+ assertThat(parallelogram.center).isEqualTo(ImmutableVec(10f, 0f))
assertThat(parallelogram.width).isEqualTo(6f)
assertThat(parallelogram.height).isEqualTo(4f)
assertThat(parallelogram.rotation).isWithin(1e-6f).of(Math.PI.toFloat())
@@ -75,7 +75,7 @@
fun equals_whenSameInstance_returnsTrueAndSameHashCode() {
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(10f, 10f),
+ ImmutableVec(10f, 10f),
12f,
2f,
Angle.HALF_TURN_RADIANS,
@@ -89,7 +89,7 @@
fun equals_whenSameValues_returnsTrueAndSameHashCode() {
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-10f, 10f),
+ ImmutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -97,7 +97,7 @@
)
val other =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-10f, 10f),
+ ImmutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -113,13 +113,13 @@
// An axis-aligned rectangle with center at (0,0) and width and height equal to 2
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(0f, 0f),
+ ImmutableVec(0f, 0f),
2f,
2f,
Angle.ZERO,
0f,
)
- val other = ImmutableBox.fromTwoPoints(ImmutablePoint(-1f, -1f), ImmutablePoint(1f, 1f))
+ val other = ImmutableBox.fromTwoPoints(ImmutableVec(-1f, -1f), ImmutableVec(1f, 1f))
assertThat(parallelogram).isNotEqualTo(other)
}
@@ -128,7 +128,7 @@
fun equals_whenDifferentCenter_returnsFalse() {
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-10f, 10f),
+ ImmutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -136,7 +136,7 @@
)
val other =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(10f, -10.5f),
+ ImmutableVec(10f, -10.5f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -150,7 +150,7 @@
fun equals_whenDifferentWidth_returnsFalse() {
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-10f, 10f),
+ ImmutableVec(-10f, 10f),
11f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -158,7 +158,7 @@
)
val other =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-10f, 10f),
+ ImmutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -172,7 +172,7 @@
fun equals_whenDifferentHeight_returnsFalse() {
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-10f, 10f),
+ ImmutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -180,7 +180,7 @@
)
val other =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-10f, 10f),
+ ImmutableVec(-10f, 10f),
12f,
7.5f,
Angle.HALF_TURN_RADIANS,
@@ -194,7 +194,7 @@
fun equals_whenDifferentRotation_returnsFalse() {
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-10f, 10f),
+ ImmutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -202,7 +202,7 @@
)
val other =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-10f, 10f),
+ ImmutableVec(-10f, 10f),
12f,
-7.5f,
Angle.QUARTER_TURN_RADIANS,
@@ -216,7 +216,7 @@
fun equals_whenDifferentShearFactor_returnsFalse() {
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-10f, 10f),
+ ImmutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -224,7 +224,7 @@
)
val other =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(-10f, 10f),
+ ImmutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -238,14 +238,14 @@
fun getters_returnCorrectValues() {
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(3f, -5f),
+ ImmutableVec(3f, -5f),
8f,
-1f,
Angle.HALF_TURN_RADIANS,
0f,
)
- assertThat(parallelogram.center).isEqualTo(ImmutablePoint(3f, -5f))
+ assertThat(parallelogram.center).isEqualTo(ImmutableVec(3f, -5f))
assertThat(parallelogram.width).isEqualTo(8f)
assertThat(parallelogram.height).isEqualTo(-1f)
assertThat(parallelogram.rotation).isEqualTo(Angle.HALF_TURN_RADIANS)
@@ -255,22 +255,22 @@
@Test
fun signedArea_returnsCorrectValue() {
val parallelogram =
- ImmutableParallelogram.fromCenterAndDimensions(ImmutablePoint(0f, 10f), 6f, 4f)
+ ImmutableParallelogram.fromCenterAndDimensions(ImmutableVec(0f, 10f), 6f, 4f)
val degenerateParallelogram =
- ImmutableParallelogram.fromCenterAndDimensions(ImmutablePoint(0f, 10f), 0f, 4f)
+ ImmutableParallelogram.fromCenterAndDimensions(ImmutableVec(0f, 10f), 0f, 4f)
val negativeAreaParallelogram =
- ImmutableParallelogram.fromCenterAndDimensions(ImmutablePoint(0f, 10f), 2f, -3f)
+ ImmutableParallelogram.fromCenterAndDimensions(ImmutableVec(0f, 10f), 2f, -3f)
- assertThat(parallelogram.signedArea()).isEqualTo(24f)
- assertThat(degenerateParallelogram.signedArea()).isZero()
- assertThat(negativeAreaParallelogram.signedArea()).isEqualTo(-6f)
+ assertThat(parallelogram.computeSignedArea()).isEqualTo(24f)
+ assertThat(degenerateParallelogram.computeSignedArea()).isZero()
+ assertThat(negativeAreaParallelogram.computeSignedArea()).isEqualTo(-6f)
}
@Test
fun toString_returnsCorrectValue() {
val parallelogramString =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(3f, -5f),
+ ImmutableVec(3f, -5f),
8f,
-1f,
Angle.HALF_TURN_RADIANS,
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutablePointTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutablePointTest.kt
deleted file mode 100644
index 564f9bd..0000000
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutablePointTest.kt
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2024 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.ink.geometry
-
-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 ImmutablePointTest {
-
- @Test
- fun equals_whenSameInstance_returnsTrueAndSameHashCode() {
- val point = ImmutablePoint(1f, 2f)
-
- assertThat(point).isEqualTo(point)
- assertThat(point.hashCode()).isEqualTo(point.hashCode())
- }
-
- @Test
- fun equals_whenDifferentType_returnsFalse() {
- val point = ImmutablePoint(1f, 2f)
- val other = ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
- assertThat(point).isNotEqualTo(other)
- }
-
- @Test
- fun equals_whenSameValues_returnsTrueAndSameHashCode() {
- val point = ImmutablePoint(-3f, 1.2f)
- val other = ImmutablePoint(-3f, 1.2f)
-
- assertThat(point).isEqualTo(other)
- assertThat(point.hashCode()).isEqualTo(other.hashCode())
- }
-
- @Test
- fun equals_whenFlippedValues_returnsFalse() {
- val point = ImmutablePoint(10f, 2134f)
- val other = ImmutablePoint(2134f, 10f)
-
- assertThat(point).isNotEqualTo(other)
- }
-
- @Test
- fun getters_returnCorrectValues() {
- val point = ImmutablePoint(10f, 2134f)
-
- assertThat(point.x).isEqualTo(10f)
- assertThat(point.y).isEqualTo(2134f)
- }
-
- @Test
- fun newMutable_returnsCorrectMutablePoint() {
- val point = ImmutablePoint(2.1f, 2134f)
-
- assertThat(point.newMutable()).isEqualTo(MutablePoint(2.1f, 2134f))
- }
-
- @Test
- fun fillMutable_correctlyModifiesMutablePoint() {
- val point = ImmutablePoint(2.1f, 2134f)
- val output = MutablePoint()
-
- point.fillMutable(output)
-
- assertThat(output).isEqualTo(MutablePoint(2.1f, 2134f))
- }
-
- @Test
- fun getVec_correctlyModifiesMutableVec() {
- val point = ImmutablePoint(65.26f, -9228f)
- val output = MutableVec()
-
- point.getVec(output)
-
- assertThat(output).isEqualTo(MutableVec(65.26f, -9228f))
- }
-
- @Test
- fun copy_withNoArguments_returnsThis() {
- val point = ImmutablePoint(1f, 2f)
-
- assertThat(point.copy()).isSameInstanceAs(point)
- }
-
- @Test
- fun copy_withArguments_makesCopy() {
- val x = 1f
- val y = 2f
- val point = ImmutablePoint(x, y)
- val differentX = 3f
- val differentY = 4f
-
- // Change both x and y.
- assertThat(point.copy(x = differentX, y = differentY))
- .isEqualTo(ImmutablePoint(differentX, differentY))
-
- // Change x.
- assertThat(point.copy(x = differentX)).isEqualTo(ImmutablePoint(differentX, y))
-
- // Change y.
- assertThat(point.copy(y = differentY)).isEqualTo(ImmutablePoint(x, differentY))
- }
-
- @Test
- fun add_withPointThenVec_correctlyAddsAndFillsMutablePoint() {
- val point = ImmutablePoint(10f, 40f)
- val vec = ImmutableVec(5f, -2f)
- val output = MutablePoint()
-
- Point.add(point, vec, output)
-
- assertThat(output).isEqualTo(MutablePoint(15f, 38f))
- }
-
- @Test
- fun add_withVecThenPoint_correctlyAddsAndFillsMutablePoint() {
- val point = ImmutablePoint(10f, 40f)
- val vec = ImmutableVec(5f, -2f)
- val output = MutablePoint()
-
- Point.add(vec, point, output)
-
- assertThat(output).isEqualTo(MutablePoint(15f, 38f))
- }
-
- @Test
- fun subtract_pointMinusVec_correctlySubtractsAndFillsMutablePoint() {
- val point = ImmutablePoint(10f, 40f)
- val vec = ImmutableVec(5f, -2f)
- val output = MutablePoint()
-
- Point.subtract(point, vec, output)
-
- assertThat(output).isEqualTo(MutablePoint(5f, 42f))
- }
-
- @Test
- fun subtract_pointMinusPoint_correctlySubtractsAndFillsMutableVec() {
- val lhsPoint = ImmutablePoint(10f, 40f)
- val rhsPoint = ImmutablePoint(5f, -2f)
- val output = MutableVec()
-
- Point.subtract(lhsPoint, rhsPoint, output)
-
- assertThat(output).isEqualTo(MutableVec(5f, 42f))
- }
-}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableSegmentTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableSegmentTest.kt
index 23ca105..a1dc1c6 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableSegmentTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableSegmentTest.kt
@@ -25,14 +25,6 @@
class ImmutableSegmentTest {
@Test
- fun vec_whenPrimaryValuesAreUnchanged_returnsSameInstance() {
- val segment = ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(1f, 2f))
- val vec = segment.vec
-
- assertThat(vec).isSameInstanceAs(segment.vec)
- }
-
- @Test
fun equals_whenSameInstance_returnsTrueAndSameHashCode() {
val segment = ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(1f, 2f))
@@ -96,18 +88,7 @@
}
@Test
- fun asImmutable_withDifferentValues_returnsNewInstance() {
- val segment = ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(1f, 2f))
- val newStart = ImmutableVec(10f, 20f)
- val newEnd = ImmutableVec(30f, 40f)
- val output = segment.asImmutable(newStart, newEnd)
-
- assertThat(output.start).isSameInstanceAs(newStart)
- assertThat(output.end).isSameInstanceAs(newEnd)
- }
-
- @Test
- fun isAlmostEqual_usesTolereneceToCompareValues() {
+ fun isAlmostEqual_usesToleranceToCompareValues() {
val segment = ImmutableSegment(ImmutableVec(1f, 2f), ImmutableVec(3f, 4f))
val other = ImmutableSegment(ImmutableVec(1.01f, 2.02f), ImmutableVec(3.03f, 4.04f))
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableTriangleTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableTriangleTest.kt
index a393d21..fdab00e 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableTriangleTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableTriangleTest.kt
@@ -94,12 +94,12 @@
fun edge_returnsCorrectSegment() {
val triangle = ImmutableTriangle(P0, P1, P2)
- assertThat(triangle.edge(0)).isEqualTo(ImmutableSegment(P0, P1))
- assertThat(triangle.edge(1)).isEqualTo(ImmutableSegment(P1, P2))
- assertThat(triangle.edge(2)).isEqualTo(ImmutableSegment(P2, P0))
- assertThat(triangle.edge(3)).isEqualTo(ImmutableSegment(P0, P1))
- assertThat(triangle.edge(4)).isEqualTo(ImmutableSegment(P1, P2))
- assertThat(triangle.edge(5)).isEqualTo(ImmutableSegment(P2, P0))
+ assertThat(triangle.computeEdge(0)).isEqualTo(ImmutableSegment(P0, P1))
+ assertThat(triangle.computeEdge(1)).isEqualTo(ImmutableSegment(P1, P2))
+ assertThat(triangle.computeEdge(2)).isEqualTo(ImmutableSegment(P2, P0))
+ assertThat(triangle.computeEdge(3)).isEqualTo(ImmutableSegment(P0, P1))
+ assertThat(triangle.computeEdge(4)).isEqualTo(ImmutableSegment(P1, P2))
+ assertThat(triangle.computeEdge(5)).isEqualTo(ImmutableSegment(P2, P0))
}
@Test
@@ -124,8 +124,8 @@
val segment0 = MutableSegment()
val segment6 = MutableSegment()
- triangle.populateEdge(0, segment0)
- triangle.populateEdge(6, segment6)
+ triangle.computeEdge(0, segment0)
+ triangle.computeEdge(6, segment6)
assertThat(segment0).isEqualTo(ImmutableSegment(P0, P1))
assertThat(segment6).isEqualTo(ImmutableSegment(P0, P1))
@@ -137,8 +137,8 @@
val segment1 = MutableSegment()
val segment7 = MutableSegment()
- triangle.populateEdge(1, segment1)
- triangle.populateEdge(7, segment7)
+ triangle.computeEdge(1, segment1)
+ triangle.computeEdge(7, segment7)
assertThat(segment1).isEqualTo(ImmutableSegment(P1, P2))
assertThat(segment7).isEqualTo(ImmutableSegment(P1, P2))
@@ -150,8 +150,8 @@
val segment2 = MutableSegment()
val segment8 = MutableSegment()
- triangle.populateEdge(2, segment2)
- triangle.populateEdge(8, segment8)
+ triangle.computeEdge(2, segment2)
+ triangle.computeEdge(8, segment8)
assertThat(segment2).isEqualTo(ImmutableSegment(P2, P0))
assertThat(segment8).isEqualTo(ImmutableSegment(P2, P0))
@@ -166,28 +166,7 @@
}
@Test
- fun asImmutable_withSameValues_returnsSelf() {
- val triangle = ImmutableTriangle(P0, P1, P2)
- val output = triangle.asImmutable(P0, P1, P2)
-
- assertThat(output).isSameInstanceAs(triangle)
- }
-
- @Test
- fun asImmutable_withDifferentValues_returnsNewInstance() {
- val triangle = ImmutableTriangle(P0, P1, P2)
- val p0 = ImmutableVec(10f, 20f)
- val p1 = ImmutableVec(30f, 40f)
- val p2 = ImmutableVec(50f, 60f)
- val output = triangle.asImmutable(p0, p1, p2)
-
- assertThat(output.p0).isSameInstanceAs(p0)
- assertThat(output.p1).isSameInstanceAs(p1)
- assertThat(output.p2).isSameInstanceAs(p2)
- }
-
- @Test
- fun isAlmostEqual_usesTolereneceToCompareValues() {
+ fun isAlmostEqual_usesToleranceToCompareValues() {
val triangle =
ImmutableTriangle(ImmutableVec(1f, 2f), ImmutableVec(3f, 4f), ImmutableVec(5f, 6f))
val other =
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableVecTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableVecTest.kt
index e6cba99..1990a13 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableVecTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ImmutableVecTest.kt
@@ -36,9 +36,9 @@
@Test
fun equals_whenDifferentType_returnsFalse() {
val vec = ImmutableVec(1f, 2f)
- val point = ImmutablePoint(1f, 2f)
+ val segment = ImmutableSegment(ImmutableVec(1f, 2f), ImmutableVec(3f, 4f))
- assertThat(vec).isNotEqualTo(point)
+ assertThat(vec).isNotEqualTo(segment)
}
@Test
@@ -74,106 +74,75 @@
}
@Test
- fun newMutable_returnsCorrectMutableVec() {
- val vec = ImmutableVec(2.1f, 2134f)
-
- assertThat(vec.newMutable()).isEqualTo(MutableVec(2.1f, 2134f))
- }
-
- @Test
- fun fillMutable_correctlyModifiesMutableVec() {
- val vec = ImmutableVec(2.1f, 2134f)
- val output = MutableVec()
-
- vec.fillMutable(output)
-
- assertThat(output).isEqualTo(MutableVec(2.1f, 2134f))
- }
-
- @Test
fun orthogonal_returnsCorrectValue() {
- assertThat(ImmutableVec(3f, 1f).orthogonal).isEqualTo(ImmutableVec(-1f, 3f))
- assertThat(ImmutableVec(-395f, .005f).orthogonal).isEqualTo(ImmutableVec(-.005f, -395f))
- assertThat(ImmutableVec(-.2f, -.66f).orthogonal).isEqualTo(ImmutableVec(.66f, -.2f))
- assertThat(ImmutableVec(123f, -987f).orthogonal).isEqualTo(ImmutableVec(987f, 123f))
+ assertThat(ImmutableVec(3f, 1f).computeOrthogonal()).isEqualTo(ImmutableVec(-1f, 3f))
+ assertThat(ImmutableVec(-395f, .005f).computeOrthogonal())
+ .isEqualTo(ImmutableVec(-.005f, -395f))
+ assertThat(ImmutableVec(-.2f, -.66f).computeOrthogonal())
+ .isEqualTo(ImmutableVec(.66f, -.2f))
+ assertThat(ImmutableVec(123f, -987f).computeOrthogonal())
+ .isEqualTo(ImmutableVec(987f, 123f))
}
@Test
fun populateOrthogonal_populatesCorrectValue() {
val mutableVec = MutableVec()
- ImmutableVec(3f, 1f).populateOrthogonal(mutableVec)
+ ImmutableVec(3f, 1f).computeOrthogonal(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(-1f, 3f))
- ImmutableVec(-395f, .005f).populateOrthogonal(mutableVec)
+ ImmutableVec(-395f, .005f).computeOrthogonal(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(-.005f, -395f))
- ImmutableVec(-.2f, -.66f).populateOrthogonal(mutableVec)
+ ImmutableVec(-.2f, -.66f).computeOrthogonal(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(.66f, -.2f))
- ImmutableVec(123f, -987f).populateOrthogonal(mutableVec)
+ ImmutableVec(123f, -987f).computeOrthogonal(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(987f, 123f))
}
@Test
fun negation_returnsCorrectValue() {
- assertThat(ImmutableVec(3f, 1f).negation).isEqualTo(ImmutableVec(-3f, -1f))
- assertThat(ImmutableVec(-395f, .005f).negation).isEqualTo(ImmutableVec(395f, -.005f))
- assertThat(ImmutableVec(-.2f, -.66f).negation).isEqualTo(ImmutableVec(.2f, .66f))
- assertThat(ImmutableVec(123f, -987f).negation).isEqualTo(ImmutableVec(-123f, 987f))
+ assertThat(ImmutableVec(3f, 1f).computeNegation()).isEqualTo(ImmutableVec(-3f, -1f))
+ assertThat(ImmutableVec(-395f, .005f).computeNegation())
+ .isEqualTo(ImmutableVec(395f, -.005f))
+ assertThat(ImmutableVec(-.2f, -.66f).computeNegation()).isEqualTo(ImmutableVec(.2f, .66f))
+ assertThat(ImmutableVec(123f, -987f).computeNegation()).isEqualTo(ImmutableVec(-123f, 987f))
}
@Test
fun populateNegation_populatesCorrectValue() {
val mutableVec = MutableVec()
- ImmutableVec(3f, 1f).populateNegation(mutableVec)
+ ImmutableVec(3f, 1f).computeNegation(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(-3f, -1f))
- ImmutableVec(-395f, .005f).populateNegation(mutableVec)
+ ImmutableVec(-395f, .005f).computeNegation(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(395f, -.005f))
- ImmutableVec(-.2f, -.66f).populateNegation(mutableVec)
+ ImmutableVec(-.2f, -.66f).computeNegation(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(.2f, .66f))
- ImmutableVec(123f, -987f).populateNegation(mutableVec)
+ ImmutableVec(123f, -987f).computeNegation(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(-123f, 987f))
}
@Test
fun magnitude_returnsCorrectValue() {
- assertThat(ImmutableVec(1f, 1f).magnitude).isEqualTo(sqrt(2f))
- assertThat(ImmutableVec(-3f, 4f).magnitude).isEqualTo(5f)
- assertThat(ImmutableVec(0f, 0f).magnitude).isEqualTo(0f)
- assertThat(ImmutableVec(0f, 17f).magnitude).isEqualTo(17f)
+ assertThat(ImmutableVec(1f, 1f).computeMagnitude()).isEqualTo(sqrt(2f))
+ assertThat(ImmutableVec(-3f, 4f).computeMagnitude()).isEqualTo(5f)
+ assertThat(ImmutableVec(0f, 0f).computeMagnitude()).isEqualTo(0f)
+ assertThat(ImmutableVec(0f, 17f).computeMagnitude()).isEqualTo(17f)
}
@Test
fun magnitudeSquared_returnsCorrectValue() {
- assertThat(ImmutableVec(1f, 1f).magnitudeSquared).isEqualTo(2f)
- assertThat(ImmutableVec(3f, -4f).magnitudeSquared).isEqualTo(25f)
- assertThat(ImmutableVec(0f, 0f).magnitudeSquared).isEqualTo(0f)
- assertThat(ImmutableVec(15f, 0f).magnitudeSquared).isEqualTo(225f)
+ assertThat(ImmutableVec(1f, 1f).computeMagnitudeSquared()).isEqualTo(2f)
+ assertThat(ImmutableVec(3f, -4f).computeMagnitudeSquared()).isEqualTo(25f)
+ assertThat(ImmutableVec(0f, 0f).computeMagnitudeSquared()).isEqualTo(0f)
+ assertThat(ImmutableVec(15f, 0f).computeMagnitudeSquared()).isEqualTo(225f)
}
@Test
- fun asImmutableVal_returnsThis() {
- val vec = ImmutableVec(1f, 2f)
-
- assertThat(vec.asImmutable).isSameInstanceAs(vec)
- }
-
- @Test
- fun asImmutableFun_withNoArguments_returnsThis() {
+ fun asImmutable_returnsThis() {
val vec = ImmutableVec(1f, 2f)
assertThat(vec.asImmutable()).isSameInstanceAs(vec)
}
@Test
- fun asImmutableFun_withArguments_returnsCorrectNewImmutableVec() {
- val vec = ImmutableVec(1f, 2f)
-
- assertThat(vec.asImmutable(x = 10f)).isEqualTo(ImmutableVec(10f, 2f))
- assertThat(vec.asImmutable(10f)).isEqualTo(ImmutableVec(10f, 2f))
- assertThat(vec.asImmutable(y = 20f)).isEqualTo(ImmutableVec(1f, 20f))
- assertThat(vec.asImmutable(x = 10f, y = 20f)).isEqualTo(ImmutableVec(10f, 20f))
- assertThat(vec.asImmutable(10f, 20f)).isEqualTo(ImmutableVec(10f, 20f))
- }
-
- @Test
fun toString_doesNotCrash() {
assertThat(ImmutableVec(1F, 2F).toString()).isNotEmpty()
}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/IntersectionTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/IntersectionTest.kt
index dd0346a..390b62d 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/IntersectionTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/IntersectionTest.kt
@@ -159,7 +159,7 @@
val shearFactor = 1f // = cotangent(PI/4), represents a 45-degree shear
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(center.x, center.y),
+ center = ImmutableVec(center.x, center.y),
width = width,
height = height,
rotation = Angle.ZERO,
@@ -210,7 +210,7 @@
fun intersects_whenPointParallelogramDoesNotIntersect_returnsFalse() {
val parallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(10f, 0f),
+ center = ImmutableVec(10f, 0f),
width = 1f,
height = 1f,
rotation = Angle.HALF_TURN_RADIANS / 4f,
@@ -236,8 +236,7 @@
@Test
fun intersects_whenPointBoxIntersects_returnsTrue() {
- val rect =
- ImmutableBox.fromTwoPoints(ImmutablePoint(3.5f, 10.9f), ImmutablePoint(2.5f, 1.1f))
+ val rect = ImmutableBox.fromTwoPoints(ImmutableVec(3.5f, 10.9f), ImmutableVec(2.5f, 1.1f))
val vertex0 = ImmutableVec(3.5f, 10.9f)
val vertex1 = MutableVec(3.5f, 1.1f)
val vertex2 = ImmutableVec(2.5f, 10.9f)
@@ -270,7 +269,7 @@
@Test
fun intersects_whenPointBoxDoesNotIntersect_returnsFalse() {
- val rect = ImmutableBox.fromTwoPoints(ImmutablePoint(-1f, 3.2f), ImmutablePoint(7f, 11.8f))
+ val rect = ImmutableBox.fromTwoPoints(ImmutableVec(-1f, 3.2f), ImmutableVec(7f, 11.8f))
val closeExteriorPoint = ImmutableVec(7.1f, 3.2f)
val farExteriorPoint = ImmutableVec(-10f, -100f)
@@ -377,11 +376,11 @@
fun intersects_whenSegmentBoxIntersects_returnsTrue() {
val segment = ImmutableSegment(start = ImmutableVec(-1f, 3.2f), end = ImmutableVec(9f, 5f))
val rectWithCommonMinPoint =
- ImmutableBox.fromTwoPoints(ImmutablePoint(-1f, 3.2f), ImmutablePoint(-10f, 0f))
+ ImmutableBox.fromTwoPoints(ImmutableVec(-1f, 3.2f), ImmutableVec(-10f, 0f))
val rectWithCommonMaxPoint =
- ImmutableBox.fromTwoPoints(ImmutablePoint(9f, 5f), ImmutablePoint(20f, 11.4f))
+ ImmutableBox.fromTwoPoints(ImmutableVec(9f, 5f), ImmutableVec(20f, 11.4f))
val intersectingBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(0f, 1f), ImmutablePoint(8f, 21f))
+ ImmutableBox.fromTwoPoints(ImmutableVec(0f, 1f), ImmutableVec(8f, 21f))
assertThat(segment.intersects(rectWithCommonMinPoint)).isTrue()
assertThat(segment.intersects(rectWithCommonMaxPoint)).isTrue()
@@ -394,9 +393,8 @@
@Test
fun intersects_whenSegmentBoxDoesNotIntersect_returnsFalse() {
val segment = ImmutableSegment(start = ImmutableVec(-1f, 3.2f), end = ImmutableVec(9f, 5f))
- val closeBox = ImmutableBox.fromTwoPoints(ImmutablePoint(9.1f, 5f), ImmutablePoint(10f, 6f))
- val farBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(-10f, -2f), ImmutablePoint(-21f, -8f))
+ val closeBox = ImmutableBox.fromTwoPoints(ImmutableVec(9.1f, 5f), ImmutableVec(10f, 6f))
+ val farBox = ImmutableBox.fromTwoPoints(ImmutableVec(-10f, -2f), ImmutableVec(-21f, -8f))
assertThat(segment.intersects(closeBox)).isFalse()
assertThat(segment.intersects(farBox)).isFalse()
@@ -409,7 +407,7 @@
val segment = ImmutableSegment(start = ImmutableVec(-1f, 3.2f), end = ImmutableVec(9f, 5f))
val parallelogramWithCommonVertex =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(1f, 6.2f),
+ center = ImmutableVec(1f, 6.2f),
width = 4f,
height = 6f,
rotation = Angle.ZERO,
@@ -417,7 +415,7 @@
)
val intersectingParallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(4f, 4.1f),
+ center = ImmutableVec(4f, 4.1f),
width = 4f,
height = 6f,
rotation = Angle.ZERO,
@@ -435,7 +433,7 @@
val segment = ImmutableSegment(start = ImmutableVec(-1f, 3.2f), end = ImmutableVec(9f, 5f))
val closeParallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(10.1f, 7f),
+ center = ImmutableVec(10.1f, 7f),
width = 2f,
height = 4f,
rotation = Angle.ZERO,
@@ -443,7 +441,7 @@
)
val farParallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(-100f, -103.1f),
+ center = ImmutableVec(-100f, -103.1f),
width = 4f,
height = 7.2f,
rotation = Angle.QUARTER_TURN_RADIANS,
@@ -547,11 +545,11 @@
p2 = ImmutableVec(4.2f, 10f),
)
val rectWithCommonP2 =
- ImmutableBox.fromTwoPoints(ImmutablePoint(4.2f, 10f), ImmutablePoint(7.9f, 19.2f))
+ ImmutableBox.fromTwoPoints(ImmutableVec(4.2f, 10f), ImmutableVec(7.9f, 19.2f))
val rectWithCommonEdge =
- ImmutableBox.fromTwoPoints(ImmutablePoint(-10f, 1f), ImmutablePoint(0f, 31.6f))
+ ImmutableBox.fromTwoPoints(ImmutableVec(-10f, 1f), ImmutableVec(0f, 31.6f))
val intersectingBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(2.1f, 20f), ImmutablePoint(6.5f, 31.9f))
+ ImmutableBox.fromTwoPoints(ImmutableVec(2.1f, 20f), ImmutableVec(6.5f, 31.9f))
assertThat(triangle.intersects(rectWithCommonP2)).isTrue()
assertThat(triangle.intersects(rectWithCommonEdge)).isTrue()
@@ -569,10 +567,8 @@
p1 = ImmutableVec(0f, 31.6f),
p2 = ImmutableVec(4.2f, 10f),
)
- val closeBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(0f, 0.9f), ImmutablePoint(-51.1f, -2f))
- val farBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(100f, 200f), ImmutablePoint(300f, 400f))
+ val closeBox = ImmutableBox.fromTwoPoints(ImmutableVec(0f, 0.9f), ImmutableVec(-51.1f, -2f))
+ val farBox = ImmutableBox.fromTwoPoints(ImmutableVec(100f, 200f), ImmutableVec(300f, 400f))
assertThat(triangle.intersects(closeBox)).isFalse()
assertThat(triangle.intersects(farBox)).isFalse()
@@ -590,7 +586,7 @@
)
val parallelogramWithCommonP1 =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(1.5f, 32.6f),
+ center = ImmutableVec(1.5f, 32.6f),
width = 3f,
height = 2f,
rotation = Angle.ZERO,
@@ -598,7 +594,7 @@
)
val parallelogramWithCommonEdge =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(-1f, 16.3f),
+ center = ImmutableVec(-1f, 16.3f),
width = 2f,
height = 15.3f,
rotation = Angle.ZERO,
@@ -606,7 +602,7 @@
)
val intersectingParallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(2.1f, 17.4f),
+ center = ImmutableVec(2.1f, 17.4f),
width = 10f,
height = 19.4f,
rotation = Angle.ZERO,
@@ -631,7 +627,7 @@
)
val closeParallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(-5.1f, 2f),
+ center = ImmutableVec(-5.1f, 2f),
width = 10f,
height = 13.2f,
rotation = Angle.ZERO,
@@ -639,7 +635,7 @@
)
val farParallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(100f, 200f),
+ center = ImmutableVec(100f, 200f),
width = 0.6f,
height = 2.3f,
rotation = Angle.QUARTER_TURN_RADIANS,
@@ -654,8 +650,8 @@
@Test
fun intersects_forEqualBoxs_returnsTrue() {
- val rect1 = ImmutableBox.fromTwoPoints(ImmutablePoint(0f, 1f), ImmutablePoint(31.6f, 10f))
- val rect2 = ImmutableBox.fromTwoPoints(ImmutablePoint(0f, 1f), ImmutablePoint(31.6f, 10f))
+ val rect1 = ImmutableBox.fromTwoPoints(ImmutableVec(0f, 1f), ImmutableVec(31.6f, 10f))
+ val rect2 = ImmutableBox.fromTwoPoints(ImmutableVec(0f, 1f), ImmutableVec(31.6f, 10f))
assertThat(rect1.intersects(rect1)).isTrue()
assertThat(rect1.intersects(rect2)).isTrue()
@@ -664,13 +660,13 @@
@Test
fun intersects_whenBoxBoxIntersects_returnsTrue() {
- val rect = ImmutableBox.fromTwoPoints(ImmutablePoint(2.1f, 1f), ImmutablePoint(31.6f, 10f))
+ val rect = ImmutableBox.fromTwoPoints(ImmutableVec(2.1f, 1f), ImmutableVec(31.6f, 10f))
val rectWithCommonVertex =
- ImmutableBox.fromTwoPoints(ImmutablePoint(2.1f, 1f), ImmutablePoint(-3f, -6.5f))
+ ImmutableBox.fromTwoPoints(ImmutableVec(2.1f, 1f), ImmutableVec(-3f, -6.5f))
val rectWithCommonEdge =
- ImmutableBox.fromTwoPoints(ImmutablePoint(31.6f, 5f), ImmutablePoint(67.9f, 2f))
+ ImmutableBox.fromTwoPoints(ImmutableVec(31.6f, 5f), ImmutableVec(67.9f, 2f))
val intersectingBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(6.7f, 3f), ImmutablePoint(20f, 100.2f))
+ ImmutableBox.fromTwoPoints(ImmutableVec(6.7f, 3f), ImmutableVec(20f, 100.2f))
assertThat(rect.intersects(rectWithCommonVertex)).isTrue()
assertThat(rect.intersects(rectWithCommonEdge)).isTrue()
@@ -682,11 +678,9 @@
@Test
fun intersects_whenBoxBoxDoesNotIntersect_returnsFalse() {
- val rect = ImmutableBox.fromTwoPoints(ImmutablePoint(2.1f, 1f), ImmutablePoint(31.6f, 10f))
- val closeBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(2f, 1f), ImmutablePoint(-10f, -11f))
- val farBox =
- ImmutableBox.fromTwoPoints(ImmutablePoint(100f, 200f), ImmutablePoint(300f, 400f))
+ val rect = ImmutableBox.fromTwoPoints(ImmutableVec(2.1f, 1f), ImmutableVec(31.6f, 10f))
+ val closeBox = ImmutableBox.fromTwoPoints(ImmutableVec(2f, 1f), ImmutableVec(-10f, -11f))
+ val farBox = ImmutableBox.fromTwoPoints(ImmutableVec(100f, 200f), ImmutableVec(300f, 400f))
assertThat(rect.intersects(closeBox)).isFalse()
assertThat(rect.intersects(farBox)).isFalse()
@@ -696,10 +690,10 @@
@Test
fun intersects_whenBoxParallelogramIntersects_returnsTrue() {
- val rect = ImmutableBox.fromTwoPoints(ImmutablePoint(2.1f, 1f), ImmutablePoint(31.6f, 10f))
+ val rect = ImmutableBox.fromTwoPoints(ImmutableVec(2.1f, 1f), ImmutableVec(31.6f, 10f))
val parallelogramWithCommonVertex =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(26.6f, 8f),
+ center = ImmutableVec(26.6f, 8f),
width = 10f,
height = 4f,
rotation = Angle.ZERO,
@@ -707,7 +701,7 @@
)
val parallelogramWithCommonEdge =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(10f, 0f),
+ center = ImmutableVec(10f, 0f),
width = 10f,
height = 2f,
rotation = Angle.ZERO,
@@ -715,7 +709,7 @@
)
val intersectingParallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(10f, 5f),
+ center = ImmutableVec(10f, 5f),
width = 6f,
height = 4f,
rotation = Angle.ZERO,
@@ -732,10 +726,10 @@
@Test
fun intersects_whenBoxParallelogramDoesNotIntersect_returnsFalse() {
- val rect = ImmutableBox.fromTwoPoints(ImmutablePoint(2.1f, 1f), ImmutablePoint(31.6f, 10f))
+ val rect = ImmutableBox.fromTwoPoints(ImmutableVec(2.1f, 1f), ImmutableVec(31.6f, 10f))
val closeParallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(0f, 1f),
+ center = ImmutableVec(0f, 1f),
width = 4f,
height = 10f,
rotation = Angle.ZERO,
@@ -743,7 +737,7 @@
)
val farParallelogram =
ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
- center = ImmutablePoint(100f, 200f),
+ center = ImmutableVec(100f, 200f),
width = 0.6f,
height = 2.3f,
rotation = Angle.QUARTER_TURN_RADIANS,
@@ -756,8 +750,105 @@
assertThat(farParallelogram.intersects(rect)).isFalse()
}
+ @Test
+ fun intersects_forEqualsParallelograms_returnsTrue() {
+ val parallelogram1 =
+ ImmutableParallelogram.fromCenterAndDimensions(
+ center = ImmutableVec(0f, 1f),
+ width = 4f,
+ height = 10f,
+ )
+ val parallelogram2 =
+ ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
+ center = ImmutableVec(0f, 1f),
+ width = 4f,
+ height = 10f,
+ rotation = Angle.ZERO,
+ shearFactor = 0f,
+ )
+
+ assertThat(parallelogram1.intersects(parallelogram1)).isTrue()
+ assertThat(parallelogram1.intersects(parallelogram2)).isTrue()
+ assertThat(parallelogram2.intersects(parallelogram1)).isTrue()
+ }
+
+ @Test
+ fun intersects_whenParallelogramParallelogramIntersects_returnsTrue() {
+ val parallelogram =
+ ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
+ center = ImmutableVec(10f, 20f),
+ width = 6f,
+ height = 4f,
+ rotation = Angle.ZERO,
+ shearFactor = 0f,
+ )
+ val parallelogramWithCommonVertex =
+ ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
+ center = ImmutableVec(6f, 16f),
+ width = 2f,
+ height = 4f,
+ rotation = Angle.ZERO,
+ shearFactor = 0f,
+ )
+ val parallelogramWithCommonEdge =
+ ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
+ center = ImmutableVec(100f, 30f),
+ width = 200f,
+ height = 16f,
+ rotation = Angle.ZERO,
+ shearFactor = 0f,
+ )
+ val intersectingParallelogram =
+ ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
+ ImmutableVec(10f, 20f),
+ 2.9f,
+ 2.1f,
+ Angle.HALF_TURN_RADIANS / 4f,
+ 0f,
+ )
+
+ assertThat(parallelogram.intersects(parallelogramWithCommonVertex)).isTrue()
+ assertThat(parallelogram.intersects(parallelogramWithCommonEdge)).isTrue()
+ assertThat(parallelogram.intersects(intersectingParallelogram)).isTrue()
+ assertThat(parallelogramWithCommonVertex.intersects(parallelogram)).isTrue()
+ assertThat(parallelogramWithCommonEdge.intersects(parallelogram)).isTrue()
+ assertThat(intersectingParallelogram.intersects(parallelogram)).isTrue()
+ }
+
+ @Test
+ fun intersects_whenParallelogramParallelogramDoesNotIntersects_returnsFalse() {
+ val parallelogram =
+ ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
+ center = ImmutableVec(10f, 20f),
+ width = 6f,
+ height = 4f,
+ rotation = Angle.ZERO,
+ shearFactor = 0f,
+ )
+ val closeParallelogram =
+ ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
+ center = ImmutableVec(0.9f, 20f),
+ width = 12f,
+ height = 4f,
+ rotation = Angle.ZERO,
+ shearFactor = 0f,
+ )
+ val farParallelogram =
+ ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
+ center = ImmutableVec(100f, 200f),
+ width = 0.6f,
+ height = 2.3f,
+ rotation = Angle.QUARTER_TURN_RADIANS,
+ shearFactor = 0f,
+ )
+
+ assertThat(parallelogram.intersects(closeParallelogram)).isFalse()
+ assertThat(parallelogram.intersects(farParallelogram)).isFalse()
+ assertThat(closeParallelogram.intersects(parallelogram)).isFalse()
+ assertThat(farParallelogram.intersects(parallelogram)).isFalse()
+ }
+
companion object {
- private val SCALE_TRANSFORM =
- ImmutableAffineTransform(a = 2f, b = 0f, c = 0f, d = 0f, e = 5f, f = 0f)
+ private val SCALE_TRANSFORM = ImmutableAffineTransform(2f, 0f, 0f, 0f, 5f, 0f)
}
}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MeshAttributeUnpackingParamsTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MeshAttributeUnpackingParamsTest.kt
index c2011d9..590e4e3 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MeshAttributeUnpackingParamsTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MeshAttributeUnpackingParamsTest.kt
@@ -138,8 +138,7 @@
)
),
)
- val notATransform =
- ImmutableBox.fromTwoPoints(ImmutablePoint(2F, 4F), ImmutablePoint(1F, 3F))
+ val notATransform = ImmutableBox.fromTwoPoints(ImmutableVec(2F, 4F), ImmutableVec(1F, 3F))
for (transform in transforms) {
assertThat(transform).isNotEqualTo(notATransform)
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MeshTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MeshTest.kt
index 5803e2b..6107970 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MeshTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MeshTest.kt
@@ -95,9 +95,9 @@
fun fillPosition_shouldThrow() {
val mesh = Mesh()
- assertFailsWith<IllegalArgumentException> { mesh.fillPosition(-1, MutablePoint()) }
- assertFailsWith<IllegalArgumentException> { mesh.fillPosition(0, MutablePoint()) }
- assertFailsWith<IllegalArgumentException> { mesh.fillPosition(1, MutablePoint()) }
+ assertFailsWith<IllegalArgumentException> { mesh.fillPosition(-1, MutableVec()) }
+ assertFailsWith<IllegalArgumentException> { mesh.fillPosition(0, MutableVec()) }
+ assertFailsWith<IllegalArgumentException> { mesh.fillPosition(1, MutableVec()) }
}
@Test
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ModeledShapeTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ModeledShapeTest.kt
deleted file mode 100644
index 7b2ede5..0000000
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ModeledShapeTest.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2024 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.ink.geometry
-
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFailsWith
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class ModeledShapeTest {
-
- @Test
- fun bounds_shouldBeEmpty() {
- val modeledShape = ModeledShape()
-
- assertThat(modeledShape.bounds).isNull()
- }
-
- @Test
- fun renderGroupCount_whenEmptyShape_shouldBeZero() {
- val modeledShape = ModeledShape()
-
- assertThat(modeledShape.renderGroupCount).isEqualTo(0)
- }
-
- @Test
- fun outlineCount_whenEmptyShape_shouldThrow() {
- val modeledShape = ModeledShape()
-
- assertFailsWith<IllegalArgumentException> { modeledShape.outlineCount(-1) }
- assertFailsWith<IllegalArgumentException> { modeledShape.outlineCount(0) }
- assertFailsWith<IllegalArgumentException> { modeledShape.outlineCount(1) }
- }
-
- @Test
- fun outlineVertexCount_whenEmptyShape_shouldThrow() {
- val modeledShape = ModeledShape()
-
- assertFailsWith<IllegalArgumentException> { modeledShape.outlineVertexCount(-1, 0) }
- assertFailsWith<IllegalArgumentException> { modeledShape.outlineVertexCount(0, 0) }
- assertFailsWith<IllegalArgumentException> { modeledShape.outlineVertexCount(1, 0) }
- }
-
- @Test
- fun fillOutlinePosition_whenEmptyShape_shouldThrow() {
- val modeledShape = ModeledShape()
-
- assertFailsWith<IllegalArgumentException> {
- modeledShape.fillOutlinePosition(-1, 0, 0, MutablePoint())
- }
- assertFailsWith<IllegalArgumentException> {
- modeledShape.fillOutlinePosition(0, 0, 0, MutablePoint())
- }
- assertFailsWith<IllegalArgumentException> {
- modeledShape.fillOutlinePosition(1, 0, 0, MutablePoint())
- }
- }
-
- @Test
- fun toString_returnsAString() {
- val string = ModeledShape().toString()
-
- // Not elaborate checks - this test mainly exists to ensure that toString doesn't crash.
- assertThat(string).contains("ModeledShape")
- assertThat(string).contains("bounds")
- assertThat(string).contains("meshes")
- assertThat(string).contains("nativeAddress")
- }
-}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableAffineTransformTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableAffineTransformTest.kt
index bc9d791..8717fd7 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableAffineTransformTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableAffineTransformTest.kt
@@ -106,6 +106,76 @@
}
@Test
+ fun setValuesAndGetValues_shouldRoundTrip() {
+ val affineTransform = MutableAffineTransform()
+ val values = floatArrayOf(1F, 2F, 3F, 4F, 5F, 6F)
+
+ affineTransform.setValues(values)
+ val outValues = FloatArray(6)
+ affineTransform.getValues(outValues)
+
+ assertThat(outValues).usingExactEquality().containsExactly(values)
+ }
+
+ @Test
+ fun constructWithValuesAndGetValues_shouldRoundTrip() {
+ val affineTransform = MutableAffineTransform(1F, 2F, 3F, 4F, 5F, 6F)
+
+ val outValues = FloatArray(6)
+ affineTransform.getValues(outValues)
+
+ assertThat(outValues)
+ .usingExactEquality()
+ .containsExactly(floatArrayOf(1F, 2F, 3F, 4F, 5F, 6F))
+ }
+
+ @Test
+ fun setValues_shouldMatchConstructedWithFactoryFunctions() {
+ assertThat(MutableAffineTransform().apply { setValues(7F, 0F, 0F, 0F, 7F, 0F) })
+ .isEqualTo(ImmutableAffineTransform.scale(7F))
+
+ assertThat(MutableAffineTransform().apply { setValues(3F, 0F, 0F, 0F, 5F, 0F) })
+ .isEqualTo(ImmutableAffineTransform.scale(3F, 5F))
+
+ assertThat(MutableAffineTransform().apply { setValues(4F, 0F, 0F, 0F, 1F, 0F) })
+ .isEqualTo(ImmutableAffineTransform.scaleX(4F))
+
+ assertThat(MutableAffineTransform().apply { setValues(1F, 0F, 0F, 0F, 2F, 0F) })
+ .isEqualTo(ImmutableAffineTransform.scaleY(2F))
+
+ assertThat(MutableAffineTransform().apply { setValues(1F, 0F, 8F, 0F, 1F, 9F) })
+ .isEqualTo(ImmutableAffineTransform.translate(ImmutableVec(8F, 9F)))
+ }
+
+ @Test
+ fun setValuesArray_shouldMatchConstructedWithFactoryFunctions() {
+ assertThat(
+ MutableAffineTransform().apply { setValues(floatArrayOf(7F, 0F, 0F, 0F, 7F, 0F)) }
+ )
+ .isEqualTo(ImmutableAffineTransform.scale(7F))
+
+ assertThat(
+ MutableAffineTransform().apply { setValues(floatArrayOf(3F, 0F, 0F, 0F, 5F, 0F)) }
+ )
+ .isEqualTo(ImmutableAffineTransform.scale(3F, 5F))
+
+ assertThat(
+ MutableAffineTransform().apply { setValues(floatArrayOf(4F, 0F, 0F, 0F, 1F, 0F)) }
+ )
+ .isEqualTo(ImmutableAffineTransform.scaleX(4F))
+
+ assertThat(
+ MutableAffineTransform().apply { setValues(floatArrayOf(1F, 0F, 0F, 0F, 2F, 0F)) }
+ )
+ .isEqualTo(ImmutableAffineTransform.scaleY(2F))
+
+ assertThat(
+ MutableAffineTransform().apply { setValues(floatArrayOf(1F, 0F, 8F, 0F, 1F, 9F)) }
+ )
+ .isEqualTo(ImmutableAffineTransform.translate(ImmutableVec(8F, 9F)))
+ }
+
+ @Test
fun asImmutable_returnsEquivalentImmutableAffineTransform() {
val affineTransform = MutableAffineTransform(A, B, C, D, E, F)
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableBoxTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableBoxTest.kt
index 94b695f..b44b015 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableBoxTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableBoxTest.kt
@@ -35,8 +35,8 @@
}
@Test
- fun fillFromCenterAndDimensions_correctlyModifiesMutableBox() {
- val rect = MutableBox().fillFromCenterAndDimensions(ImmutablePoint(20f, -50f), 10f, 20f)
+ fun populateFromCenterAndDimensions_correctlyModifiesMutableBox() {
+ val rect = MutableBox().populateFromCenterAndDimensions(ImmutableVec(20f, -50f), 10f, 20f)
assertThat(rect.xMin).isEqualTo(15f)
assertThat(rect.xMax).isEqualTo(25f)
@@ -47,9 +47,9 @@
}
@Test
- fun fillFromTwoPoints_correctlyModifiesMutableBox() {
+ fun populateFromTwoPoints_correctlyModifiesMutableBox() {
val rect =
- MutableBox().fillFromTwoPoints(MutablePoint(20f, -50f), ImmutablePoint(-70f, 100f))
+ MutableBox().populateFromTwoPoints(MutableVec(20f, -50f), ImmutableVec(-70f, 100f))
assertThat(rect.xMin).isEqualTo(-70f)
assertThat(rect.xMax).isEqualTo(20f)
@@ -61,7 +61,7 @@
@Test
fun minMaxFields_whenAllZeroes_allAreZero() {
- val zeroes = MutableBox().fillFromTwoPoints(ImmutablePoint(0F, 0F), ImmutablePoint(0F, 0F))
+ val zeroes = MutableBox().populateFromTwoPoints(ImmutableVec(0F, 0F), ImmutableVec(0F, 0F))
assertThat(zeroes.xMin).isEqualTo(0F)
assertThat(zeroes.yMin).isEqualTo(0F)
assertThat(zeroes.xMax).isEqualTo(0F)
@@ -71,7 +71,7 @@
@Test
fun minMaxFields_whenDeclaredInMinMaxOrder_matchOrder() {
val inOrder =
- MutableBox().fillFromTwoPoints(ImmutablePoint(-1F, -2F), ImmutablePoint(3F, 4F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(-1F, -2F), ImmutableVec(3F, 4F))
assertThat(inOrder.xMin).isEqualTo(-1F)
assertThat(inOrder.yMin).isEqualTo(-2F)
assertThat(inOrder.xMax).isEqualTo(3F)
@@ -81,7 +81,7 @@
@Test
fun minMaxFields_whenDeclaredOutOfOrder_doNotMatchOrder() {
val outOfOrder =
- MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(-3F, -4F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(-3F, -4F))
assertThat(outOfOrder.xMin).isEqualTo(-3F)
assertThat(outOfOrder.yMin).isEqualTo(-4F)
assertThat(outOfOrder.xMax).isEqualTo(1F)
@@ -90,7 +90,7 @@
@Test
fun widthHeight_whenAllZeroes_areAllZero() {
- val zeroes = MutableBox().fillFromTwoPoints(ImmutablePoint(0F, 0F), ImmutablePoint(0F, 0F))
+ val zeroes = MutableBox().populateFromTwoPoints(ImmutableVec(0F, 0F), ImmutableVec(0F, 0F))
assertThat(zeroes.width).isEqualTo(0)
assertThat(zeroes.height).isEqualTo(0)
@@ -99,7 +99,7 @@
@Test
fun widthHeight_whenDeclaredInOrder_areCorrectValues() {
val inOrder =
- MutableBox().fillFromTwoPoints(ImmutablePoint(-1F, -2F), ImmutablePoint(3F, 4F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(-1F, -2F), ImmutableVec(3F, 4F))
assertThat(inOrder.width).isEqualTo(4F)
assertThat(inOrder.height).isEqualTo(6F)
@@ -108,7 +108,7 @@
@Test
fun widthHeight_whenDeclaredOutOfOrder_areCorrectValues() {
val outOfOrder =
- MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(-3F, -4F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(-3F, -4F))
assertThat(outOfOrder.width).isEqualTo(4F)
assertThat(outOfOrder.height).isEqualTo(6F)
@@ -116,9 +116,9 @@
@Test
fun widthHeight_whenValuesChanged_areCorrectValues() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(-3F, -4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(-3F, -4F))
- rect.fillFromTwoPoints(MutablePoint(-20f, -5f), ImmutablePoint(30f, 7f))
+ rect.populateFromTwoPoints(MutableVec(-20f, -5f), ImmutableVec(30f, 7f))
assertThat(rect.width).isEqualTo(50F)
assertThat(rect.height).isEqualTo(12F)
@@ -126,66 +126,66 @@
@Test
fun setXBounds_whenInOrder_changesXMinAndXMax() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
rect.setXBounds(5F, 7F)
assertThat(rect)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(5F, 2F), ImmutablePoint(7F, 4F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(5F, 2F), ImmutableVec(7F, 4F))
)
}
@Test
fun setXBounds_whenNotInOrder_changesXMinAndXMax() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
rect.setXBounds(7F, 5F)
assertThat(rect)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(5F, 2F), ImmutablePoint(7F, 4F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(5F, 2F), ImmutableVec(7F, 4F))
)
}
@Test
fun setYBounds_whenInOrder_changesXMinAndXMax() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
rect.setYBounds(6F, 8F)
assertThat(rect)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 6F), ImmutablePoint(3F, 8F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(1F, 6F), ImmutableVec(3F, 8F))
)
}
@Test
fun setYBounds_whenNotInOrder_changesXMinAndXMax() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
rect.setYBounds(8F, 6F)
assertThat(rect)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 6F), ImmutablePoint(3F, 8F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(1F, 6F), ImmutableVec(3F, 8F))
)
}
@Test
fun populateFrom_correctlyPopulatesFromBox() {
- val source = ImmutableBox.fromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val source = ImmutableBox.fromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
val dest = MutableBox().populateFrom(source)
assertThat(dest)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
)
}
@Test
fun equals_whenSameInstance_returnsTrueAndSameHashCode() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(rect).isEqualTo(rect)
assertThat(rect.hashCode()).isEqualTo(rect.hashCode())
@@ -193,15 +193,15 @@
@Test
fun equals_whenDifferentType_returnsFalse() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
- assertThat(rect).isNotEqualTo(ImmutablePoint(1F, 2F))
+ assertThat(rect).isNotEqualTo(ImmutableVec(1F, 2F))
}
@Test
fun equals_whenSameValues_returnsTrueAndSameHashCode() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
- val other = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
+ val other = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(rect).isEqualTo(other)
assertThat(rect.hashCode()).isEqualTo(other.hashCode())
@@ -209,8 +209,8 @@
@Test
fun equals_whenSameValuesOutOfOrder_returnsTrueAndSameHashCode() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
- val other = MutableBox().fillFromTwoPoints(ImmutablePoint(3F, 4F), ImmutablePoint(1F, 2F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
+ val other = MutableBox().populateFromTwoPoints(ImmutableVec(3F, 4F), ImmutableVec(1F, 2F))
assertThat(rect).isEqualTo(other)
assertThat(rect.hashCode()).isEqualTo(other.hashCode())
@@ -218,63 +218,49 @@
@Test
fun equals_whenDifferentXMin_returnsFalse() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(rect)
.isNotEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(-1F, 2F), ImmutablePoint(3F, 4F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(-1F, 2F), ImmutableVec(3F, 4F))
)
}
@Test
fun equals_whenDifferentYMin_returnsFalse() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(rect)
.isNotEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(1F, -2F), ImmutablePoint(3F, 4F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(1F, -2F), ImmutableVec(3F, 4F))
)
}
@Test
fun equals_whenDifferentXMax_returnsFalse() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(rect)
.isNotEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(30F, 4F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(30F, 4F))
)
}
@Test
fun equals_whenDifferentYMax_returnsFalse() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
assertThat(rect)
.isNotEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 40F))
- )
- }
-
- @Test
- fun copy_returnsEqualValueThatCannotModifyOriginal() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
-
- val copy = rect.copy()
- assertThat(copy).isEqualTo(rect)
-
- copy.fillFromTwoPoints(ImmutablePoint(5F, 6F), ImmutablePoint(7F, 8F))
- assertThat(rect)
- .isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 40F))
)
}
@Test
fun overwriteFromValues_whenInOrder_changesAllValues() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
- rect.fillFromTwoPoints(ImmutablePoint(5F, 6F), ImmutablePoint(7F, 8F))
+ rect.populateFromTwoPoints(ImmutableVec(5F, 6F), ImmutableVec(7F, 8F))
assertThat(rect.xMin).isEqualTo(5F)
assertThat(rect.yMin).isEqualTo(6F)
@@ -284,9 +270,9 @@
@Test
fun overwriteFromValues_whenOutOfOrder_changesAllValues() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 2F), ImmutablePoint(3F, 4F))
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 2F), ImmutableVec(3F, 4F))
- rect.fillFromTwoPoints(ImmutablePoint(-1F, -2F), ImmutablePoint(-3F, -4F))
+ rect.populateFromTwoPoints(ImmutableVec(-1F, -2F), ImmutableVec(-3F, -4F))
assertThat(rect.xMin).isEqualTo(-3F)
assertThat(rect.yMin).isEqualTo(-4F)
@@ -295,35 +281,35 @@
}
@Test
- fun center_modifiesMutablePoint() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 20F), ImmutablePoint(3F, 40F))
- val outCenter = MutablePoint()
- rect.center(outCenter)
+ fun populateCenter_modifiesMutableVec() {
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 20F), ImmutableVec(3F, 40F))
+ val outCenter = MutableVec()
+ rect.computeCenter(outCenter)
- assertThat(outCenter).isEqualTo(MutablePoint(2F, 30F))
+ assertThat(outCenter).isEqualTo(MutableVec(2F, 30F))
}
@Test
fun corners_modifiesMutablePoints() {
- val rect = MutableBox().fillFromTwoPoints(ImmutablePoint(1F, 20F), ImmutablePoint(3F, 40F))
- val p0 = MutablePoint()
- val p1 = MutablePoint()
- val p2 = MutablePoint()
- val p3 = MutablePoint()
- rect.corners(p0, p1, p2, p3)
+ val rect = MutableBox().populateFromTwoPoints(ImmutableVec(1F, 20F), ImmutableVec(3F, 40F))
+ val p0 = MutableVec()
+ val p1 = MutableVec()
+ val p2 = MutableVec()
+ val p3 = MutableVec()
+ rect.computeCorners(p0, p1, p2, p3)
- assertThat(p0).isEqualTo(MutablePoint(1F, 20F))
- assertThat(p1).isEqualTo(MutablePoint(3F, 20F))
- assertThat(p2).isEqualTo(MutablePoint(3F, 40F))
- assertThat(p3).isEqualTo(MutablePoint(1F, 40F))
+ assertThat(p0).isEqualTo(MutableVec(1F, 20F))
+ assertThat(p1).isEqualTo(MutableVec(3F, 20F))
+ assertThat(p2).isEqualTo(MutableVec(3F, 40F))
+ assertThat(p3).isEqualTo(MutableVec(1F, 40F))
}
@Test
- fun contains_returnsCorrectValuesWithPoint() {
+ fun contains_returnsCorrectValuesWithVec() {
val rect =
- MutableBox().fillFromTwoPoints(ImmutablePoint(10F, 600F), ImmutablePoint(40F, 900F))
- val innerPoint = ImmutablePoint(30F, 700F)
- val outerPoint = ImmutablePoint(70F, 2000F)
+ MutableBox().populateFromTwoPoints(ImmutableVec(10F, 600F), ImmutableVec(40F, 900F))
+ val innerPoint = ImmutableVec(30F, 700F)
+ val outerPoint = ImmutableVec(70F, 2000F)
assertThat(rect.contains(innerPoint)).isTrue()
assertThat(rect.contains(outerPoint)).isFalse()
@@ -332,9 +318,9 @@
@Test
fun contains_returnsCorrectValuesWithBox() {
val outerRect =
- MutableBox().fillFromTwoPoints(ImmutablePoint(10F, 600F), ImmutablePoint(40F, 900F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(10F, 600F), ImmutableVec(40F, 900F))
val innerRect =
- MutableBox().fillFromTwoPoints(ImmutablePoint(20F, 700F), ImmutablePoint(30F, 800F))
+ MutableBox().populateFromTwoPoints(ImmutableVec(20F, 700F), ImmutableVec(30F, 800F))
assertThat(outerRect.contains(innerRect)).isTrue()
assertThat(innerRect.contains(outerRect)).isFalse()
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableParallelogramTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableParallelogramTest.kt
index 9d737bd..da24a51 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableParallelogramTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableParallelogramTest.kt
@@ -28,7 +28,7 @@
fun defaultConstructor_constructsCorrectMutableParallelogram() {
val parallelogram = MutableParallelogram()
- assertThat(parallelogram.center).isEqualTo(MutablePoint(0f, 0f))
+ assertThat(parallelogram.center).isEqualTo(MutableVec(0f, 0f))
assertThat(parallelogram.width).isZero()
assertThat(parallelogram.height).isZero()
assertThat(parallelogram.rotation).isZero()
@@ -38,7 +38,7 @@
@Test
fun setCenter_changesCenter() {
val parallelogram = MutableParallelogram()
- val newCenter = MutablePoint(5f, -2f)
+ val newCenter = MutableVec(5f, -2f)
parallelogram.center = newCenter
assertThat(parallelogram.center).isEqualTo(newCenter)
}
@@ -47,7 +47,7 @@
fun setWidth_toNegativeValue_forcesNormalizationOfParallelogram() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsAndRotation(
- MutablePoint(10f, 0f),
+ MutableVec(10f, 0f),
6f,
4f,
Angle.QUARTER_TURN_RADIANS,
@@ -65,7 +65,7 @@
fun setRotation_toOutOfRangeNormalRange_forcesNormalizationOfAngle() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsAndRotation(
- MutablePoint(10f, 0f),
+ MutableVec(10f, 0f),
6f,
4f,
Angle.QUARTER_TURN_RADIANS,
@@ -77,9 +77,9 @@
@Test
fun fromCenterAndDimensions_constructsCorrectMutableParallelogram() {
val parallelogram =
- MutableParallelogram.fromCenterAndDimensions(MutablePoint(10f, 0f), 6f, 4f)
+ MutableParallelogram.fromCenterAndDimensions(MutableVec(10f, 0f), 6f, 4f)
- assertThat(parallelogram.center).isEqualTo(MutablePoint(10f, 0f))
+ assertThat(parallelogram.center).isEqualTo(MutableVec(10f, 0f))
assertThat(parallelogram.width).isEqualTo(6f)
assertThat(parallelogram.height).isEqualTo(4f)
assertThat(parallelogram.rotation).isZero()
@@ -89,9 +89,9 @@
@Test
fun fromCenterAndDimensions_forNegativeWidth_constructsCorrectMutableParallelogram() {
val parallelogramWithNegativeWidth =
- MutableParallelogram.fromCenterAndDimensions(MutablePoint(10f, 0f), -6f, 4f)
+ MutableParallelogram.fromCenterAndDimensions(MutableVec(10f, 0f), -6f, 4f)
- assertThat(parallelogramWithNegativeWidth.center).isEqualTo(MutablePoint(10f, 0f))
+ assertThat(parallelogramWithNegativeWidth.center).isEqualTo(MutableVec(10f, 0f))
assertThat(parallelogramWithNegativeWidth.width).isEqualTo(6f)
assertThat(parallelogramWithNegativeWidth.height).isEqualTo(-4f)
assertThat(parallelogramWithNegativeWidth.rotation).isEqualTo(Math.PI.toFloat())
@@ -102,13 +102,13 @@
fun fromCenterDimensionsAndRotation_constructsCorrectMutableParallelogram() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsAndRotation(
- MutablePoint(10f, 0f),
+ MutableVec(10f, 0f),
6f,
4f,
Angle.FULL_TURN_RADIANS,
)
- assertThat(parallelogram.center).isEqualTo(MutablePoint(10f, 0f))
+ assertThat(parallelogram.center).isEqualTo(MutableVec(10f, 0f))
assertThat(parallelogram.width).isEqualTo(6f)
assertThat(parallelogram.height).isEqualTo(4f)
assertThat(parallelogram.rotation).isZero()
@@ -119,13 +119,13 @@
fun fromCenterDimensionsAndRotation_forNegativeWidth_constructsCorrectMutableParallelogram() {
val parallelogramWithNegativeWidth =
MutableParallelogram.fromCenterDimensionsAndRotation(
- MutablePoint(10f, 0f),
+ MutableVec(10f, 0f),
-6f,
4f,
Angle.FULL_TURN_RADIANS,
)
- assertThat(parallelogramWithNegativeWidth.center).isEqualTo(MutablePoint(10f, 0f))
+ assertThat(parallelogramWithNegativeWidth.center).isEqualTo(MutableVec(10f, 0f))
assertThat(parallelogramWithNegativeWidth.width).isEqualTo(6f)
assertThat(parallelogramWithNegativeWidth.height).isEqualTo(-4f)
assertThat(parallelogramWithNegativeWidth.rotation).isWithin(1e-6f).of(Math.PI.toFloat())
@@ -136,14 +136,14 @@
fun fromCenterDimensionsRotationAndShear_constructsCorrectMutableParallelogram() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(10f, 0f),
+ MutableVec(10f, 0f),
6f,
4f,
Angle.HALF_TURN_RADIANS,
1f,
)
- assertThat(parallelogram.center).isEqualTo(MutablePoint(10f, 0f))
+ assertThat(parallelogram.center).isEqualTo(MutableVec(10f, 0f))
assertThat(parallelogram.width).isEqualTo(6f)
assertThat(parallelogram.height).isEqualTo(4f)
assertThat(parallelogram.rotation).isWithin(1e-6f).of(Math.PI.toFloat())
@@ -154,14 +154,14 @@
fun fromCenterDimensionsRotationAndShear_forNegativeWidth_constructsCorrectMutableParallelogram() {
val parallelogramWithNegativeWidth =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(10f, 0f),
+ MutableVec(10f, 0f),
-6f,
4f,
Angle.FULL_TURN_RADIANS,
1f,
)
- assertThat(parallelogramWithNegativeWidth.center).isEqualTo(MutablePoint(10f, 0f))
+ assertThat(parallelogramWithNegativeWidth.center).isEqualTo(MutableVec(10f, 0f))
assertThat(parallelogramWithNegativeWidth.width).isEqualTo(6f)
assertThat(parallelogramWithNegativeWidth.height).isEqualTo(-4f)
assertThat(parallelogramWithNegativeWidth.rotation).isWithin(1e-6f).of(Math.PI.toFloat())
@@ -172,7 +172,7 @@
fun equals_whenSameInstance_returnsTrueAndSameHashCode() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(10f, 10f),
+ MutableVec(10f, 10f),
12f,
2f,
Angle.HALF_TURN_RADIANS,
@@ -186,7 +186,7 @@
fun equals_whenSameValues_returnsTrueAndSameHashCode() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(-10f, 10f),
+ MutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -194,7 +194,7 @@
)
val other =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(-10f, 10f),
+ MutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -210,13 +210,13 @@
// An axis-aligned rectangle with center at (0,0) and width and height equal to 2
val parallelogram =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(0f, 0f),
+ MutableVec(0f, 0f),
2f,
2f,
Angle.ZERO,
0f,
)
- val other = MutableBox().fillFromTwoPoints(ImmutablePoint(-1f, -1f), ImmutablePoint(1f, 1f))
+ val other = MutableBox().populateFromTwoPoints(ImmutableVec(-1f, -1f), ImmutableVec(1f, 1f))
assertThat(parallelogram).isNotEqualTo(other)
}
@@ -225,7 +225,7 @@
fun equals_whenDifferentCenter_returnsFalse() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(-10f, 10f),
+ MutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -233,7 +233,7 @@
)
val other =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(10f, -10.5f),
+ MutableVec(10f, -10.5f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -247,7 +247,7 @@
fun equals_whenDifferentWidth_returnsFalse() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(-10f, 10f),
+ MutableVec(-10f, 10f),
11f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -255,7 +255,7 @@
)
val other =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(-10f, 10f),
+ MutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -269,7 +269,7 @@
fun equals_whenDifferentHeight_returnsFalse() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(-10f, 10f),
+ MutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -277,7 +277,7 @@
)
val other =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(-10f, 10f),
+ MutableVec(-10f, 10f),
12f,
7.5f,
Angle.HALF_TURN_RADIANS,
@@ -291,7 +291,7 @@
fun equals_whenDifferentRotation_returnsFalse() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(-10f, 10f),
+ MutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -299,7 +299,7 @@
)
val other =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(-10f, 10f),
+ MutableVec(-10f, 10f),
12f,
-7.5f,
Angle.QUARTER_TURN_RADIANS,
@@ -313,7 +313,7 @@
fun equals_whenDifferentShearFactor_returnsFalse() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(-10f, 10f),
+ MutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -321,7 +321,7 @@
)
val other =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(-10f, 10f),
+ MutableVec(-10f, 10f),
12f,
-7.5f,
Angle.HALF_TURN_RADIANS,
@@ -335,14 +335,14 @@
fun getters_returnCorrectValues() {
val parallelogram =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- MutablePoint(3f, -5f),
+ MutableVec(3f, -5f),
8f,
-1f,
Angle.HALF_TURN_RADIANS,
0f,
)
- assertThat(parallelogram.center).isEqualTo(MutablePoint(3f, -5f))
+ assertThat(parallelogram.center).isEqualTo(MutableVec(3f, -5f))
assertThat(parallelogram.width).isEqualTo(8f)
assertThat(parallelogram.height).isEqualTo(-1f)
assertThat(parallelogram.rotation).isEqualTo(Angle.HALF_TURN_RADIANS)
@@ -352,22 +352,22 @@
@Test
fun signedArea_returnsCorrectValue() {
val parallelogram =
- MutableParallelogram.fromCenterAndDimensions(MutablePoint(0f, 10f), 6f, 4f)
+ MutableParallelogram.fromCenterAndDimensions(MutableVec(0f, 10f), 6f, 4f)
val degenerateParallelogram =
- MutableParallelogram.fromCenterAndDimensions(MutablePoint(0f, 10f), 0f, 4f)
+ MutableParallelogram.fromCenterAndDimensions(MutableVec(0f, 10f), 0f, 4f)
val negativeAreaParallelogram =
- MutableParallelogram.fromCenterAndDimensions(MutablePoint(0f, 10f), 2f, -3f)
+ MutableParallelogram.fromCenterAndDimensions(MutableVec(0f, 10f), 2f, -3f)
- assertThat(parallelogram.signedArea()).isEqualTo(24f)
- assertThat(degenerateParallelogram.signedArea()).isZero()
- assertThat(negativeAreaParallelogram.signedArea()).isEqualTo(-6f)
+ assertThat(parallelogram.computeSignedArea()).isEqualTo(24f)
+ assertThat(degenerateParallelogram.computeSignedArea()).isZero()
+ assertThat(negativeAreaParallelogram.computeSignedArea()).isEqualTo(-6f)
}
@Test
fun toString_returnsCorrectValue() {
val parallelogramString =
MutableParallelogram.fromCenterDimensionsRotationAndShear(
- ImmutablePoint(3f, -5f),
+ MutableVec(3f, -5f),
8f,
-1f,
Angle.HALF_TURN_RADIANS,
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutablePointTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutablePointTest.kt
deleted file mode 100644
index 2279b4b..0000000
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutablePointTest.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2024 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.ink.geometry
-
-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 MutablePointTest {
-
- @Test
- fun equals_whenSameInstance_returnsTrueAndSameHashCode() {
- val point = MutablePoint(1f, 2f)
-
- assertThat(point).isEqualTo(point)
- assertThat(point.hashCode()).isEqualTo(point.hashCode())
- }
-
- @Test
- fun equals_whenDifferentType_returnsFalse() {
- val point = MutablePoint(1f, 2f)
- val vec = ImmutableVec(1f, 2f)
-
- assertThat(point).isNotEqualTo(vec)
- }
-
- @Test
- fun equals_whenSameInterface_returnsTrue() {
- val point = MutablePoint(1f, 2f)
- val other = ImmutablePoint(1f, 2f)
-
- assertThat(point).isEqualTo(other)
- }
-
- @Test
- fun equals_whenSameValues_returnsTrueAndSameHashCode() {
- val point = MutablePoint(-3f, 1.2f)
- val other = MutablePoint(-3f, 1.2f)
-
- assertThat(point).isEqualTo(other)
- assertThat(point.hashCode()).isEqualTo(other.hashCode())
- }
-
- @Test
- fun equals_whenFlippedValues_returnsFalse() {
- val point = MutablePoint(10f, 2134f)
- val other = MutablePoint(2134f, 10f)
-
- assertThat(point).isNotEqualTo(other)
- }
-
- @Test
- fun getters_returnCorrectValues() {
- val point = MutablePoint(10f, 2134f)
-
- assertThat(point.x).isEqualTo(10f)
- assertThat(point.y).isEqualTo(2134f)
- }
-
- @Test
- fun setters_gettersReturnNewValues() {
- val point = MutablePoint(99f, 1234f)
-
- point.x = 10f
- point.y = 2134f
-
- assertThat(point.x).isEqualTo(10f)
- assertThat(point.y).isEqualTo(2134f)
- }
-
- @Test
- fun build_returnsPointWithSameValues() {
- val point = MutablePoint(10f, 2134f)
-
- val builtPoint = point.build()
- assertThat(builtPoint).isEqualTo(ImmutablePoint(10f, 2134f))
- }
-
- @Test
- fun add_withPointThenVec_correctlyAddsAndFillsAndDoesntMutateInputs() {
- val point = MutablePoint(10f, 40f)
- val vec = MutableVec(5f, -2f)
- val output = MutablePoint()
-
- Point.add(point, vec, output)
-
- assertThat(output).isEqualTo(MutablePoint(15f, 38f))
- assertThat(point).isEqualTo(MutablePoint(10f, 40f))
- assertThat(vec).isEqualTo(MutableVec(5f, -2f))
- }
-
- @Test
- fun add_withVecThenPoint_correctlyAddsAndFillsAndDoesntMutateInputs() {
- val point = MutablePoint(10f, 40f)
- val vec = MutableVec(5f, -2f)
- val output = MutablePoint()
-
- Point.add(vec, point, output)
-
- assertThat(output).isEqualTo(MutablePoint(15f, 38f))
- assertThat(point).isEqualTo(MutablePoint(10f, 40f))
- assertThat(vec).isEqualTo(MutableVec(5f, -2f))
- }
-
- @Test
- fun subtract_pointMinusVec_correctlySubtractsAndFillsAndDoesntMutateInputs() {
- val point = MutablePoint(10f, 40f)
- val vec = MutableVec(5f, -2f)
- val output = MutablePoint()
-
- Point.subtract(point, vec, output)
-
- assertThat(output).isEqualTo(MutablePoint(5f, 42f))
- assertThat(point).isEqualTo(MutablePoint(10f, 40f))
- assertThat(vec).isEqualTo(MutableVec(5f, -2f))
- }
-
- @Test
- fun subtract_pointMinusPoint_correctlySubtractsAndFillsAndDoesntMutateInputs() {
- val lhsPoint = MutablePoint(10f, 40f)
- val rhsPoint = MutablePoint(5f, -2f)
- val output = MutableVec()
-
- Point.subtract(lhsPoint, rhsPoint, output)
-
- assertThat(output).isEqualTo(MutableVec(5f, 42f))
- assertThat(lhsPoint).isEqualTo(MutablePoint(10f, 40f))
- assertThat(rhsPoint).isEqualTo(MutablePoint(5f, -2f))
- }
-}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableSegmentTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableSegmentTest.kt
index 39320e1..c3ce145 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableSegmentTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableSegmentTest.kt
@@ -28,18 +28,20 @@
fun vec_whenPrimaryValuesAreUnchanged_returnsCorrectImmutableVec() {
val segment = MutableSegment(MutableVec(0f, 0f), MutableVec(1f, 2f))
- assertThat(segment.vec).isEqualTo(ImmutableVec(1f, 2f))
+ assertThat(segment.computeDisplacement()).isEqualTo(ImmutableVec(1f, 2f))
}
@Test
fun vec_whenPrimaryValuesAreModified_returnsDifferentImmutableVec() {
val segment = MutableSegment(MutableVec(10f, 50f), MutableVec(1f, 2f))
- segment.start(0f, 0f)
- assertThat(segment.vec).isEqualTo(ImmutableVec(1f, 2f))
+ segment.start.x = 0f
+ segment.start.y = 0f
+ assertThat(segment.computeDisplacement()).isEqualTo(ImmutableVec(1f, 2f))
- segment.end(-.005f, -456f)
- assertThat(segment.vec).isEqualTo(ImmutableVec(-.005f, -456f))
+ segment.end.x = -.005f
+ segment.end.y = -456f
+ assertThat(segment.computeDisplacement()).isEqualTo(ImmutableVec(-.005f, -456f))
}
@Test
@@ -90,42 +92,6 @@
}
@Test
- fun start_correctlyModifiesStartValue() {
- val segment = MutableSegment(MutableVec(10f, 20f), MutableVec(1f, 2f))
-
- segment.start(ImmutableVec(1.5f, 21.6f))
-
- assertThat(segment.start).isEqualTo(MutableVec(1.5f, 21.6f))
- }
-
- @Test
- fun start_withXYArgs_correctlyModifiesStartValue() {
- val segment = MutableSegment(MutableVec(10f, 20f), MutableVec(1f, 2f))
-
- segment.start(x = 1.5f, y = 21.6f)
-
- assertThat(segment.start).isEqualTo(MutableVec(1.5f, 21.6f))
- }
-
- @Test
- fun end_correctlyModifiesEndValue() {
- val segment = MutableSegment(MutableVec(10f, 20f), MutableVec(1f, 2f))
-
- segment.end(ImmutableVec(-1.5f, -21.6f))
-
- assertThat(segment.end).isEqualTo(MutableVec(-1.5f, -21.6f))
- }
-
- @Test
- fun end_withXYArgs_correctlyModifiesEndValue() {
- val segment = MutableSegment(MutableVec(10f, 20f), MutableVec(1f, 2f))
-
- segment.end(x = -1.5f, y = -21.6f)
-
- assertThat(segment.end).isEqualTo(MutableVec(-1.5f, -21.6f))
- }
-
- @Test
fun asImmutable_returnsImmutableCopy() {
val start = MutableVec(10f, 20f)
val end = MutableVec(1f, 2f)
@@ -137,18 +103,7 @@
}
@Test
- fun asImmutable_withNewValues_ReturnsNewImmutable() {
- val segment = MutableSegment(MutableVec(0f, 0f), MutableVec(-100f, -200f))
- val newStart = ImmutableVec(10f, 20f)
- val newEnd = ImmutableVec(30f, 40f)
- val output = segment.asImmutable(newStart, newEnd)
-
- assertThat(output.start).isEqualTo(newStart)
- assertThat(output.end).isEqualTo(newEnd)
- }
-
- @Test
- fun isAlmostEqual_usesTolereneceToCompareValues() {
+ fun isAlmostEqual_usesToleranceToCompareValues() {
val segment = MutableSegment(MutableVec(1f, 2f), MutableVec(3f, 4f))
val other = MutableSegment(MutableVec(1.01f, 2.02f), MutableVec(3.03f, 4.04f))
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableTriangleTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableTriangleTest.kt
index 9984cde..028443d 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableTriangleTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableTriangleTest.kt
@@ -24,9 +24,13 @@
@RunWith(JUnit4::class)
class MutableTriangleTest {
+ private val p0 = MutableVec(1f, 2f)
+ private val p1 = MutableVec(5f, 2f)
+ private val p2 = MutableVec(5f, 6f)
+
@Test
fun equals_whenSameInstance_returnsTrueAndSameHashCode() {
- val triangle = MutableTriangle(P0.newMutable(), P1.newMutable(), P2.newMutable())
+ val triangle = MutableTriangle(p0, p1, p2)
// Ensure test coverage of the same-instance case, but call .equals directly for lint.
assertThat(triangle.equals(triangle)).isTrue()
@@ -34,8 +38,9 @@
@Test
fun equals_whenSameValues_returnsTrueAndSameHashCode() {
- val triangle = MutableTriangle(P0.newMutable(), P1.newMutable(), P2.newMutable())
- val other = MutableTriangle(P0.newMutable(), P1.newMutable(), P2.newMutable())
+ val triangle = MutableTriangle(p0, p1, p2)
+ val other =
+ MutableTriangle(MutableVec(p0.x, p0.y), MutableVec(p1.x, p1.y), MutableVec(p2.x, p2.y))
assertThat(triangle).isEqualTo(other)
assertThat(triangle.hashCode()).isEqualTo(other.hashCode())
@@ -43,11 +48,9 @@
@Test
fun equals_whenPermutedEndpoints_returnsFalse() {
- val triangle = MutableTriangle(P0.newMutable(), P1.newMutable(), P2.newMutable())
- val clockWisePermutation =
- MutableTriangle(P1.newMutable(), P2.newMutable(), P0.newMutable())
- val counterClockWisePermutation =
- MutableTriangle(P2.newMutable(), P0.newMutable(), P1.newMutable())
+ val triangle = MutableTriangle(p0, p1, p2)
+ val clockWisePermutation = MutableTriangle(p1, p2, p0)
+ val counterClockWisePermutation = MutableTriangle(p2, p0, p1)
assertThat(triangle).isNotEqualTo(clockWisePermutation)
assertThat(triangle).isNotEqualTo(counterClockWisePermutation)
@@ -55,9 +58,9 @@
@Test
fun equals_whenP0different_returnsFalse() {
- val triangle = MutableTriangle(MutableVec(1f, 2f), P1.newMutable(), P2.newMutable())
- val p0XChange = MutableTriangle(MutableVec(1.23f, 2f), P1.newMutable(), P2.newMutable())
- val p0YChange = MutableTriangle(MutableVec(1f, 21.1f), P1.newMutable(), P2.newMutable())
+ val triangle = MutableTriangle(MutableVec(1f, 2f), p1, p2)
+ val p0XChange = MutableTriangle(MutableVec(1.23f, 2f), p1, p2)
+ val p0YChange = MutableTriangle(MutableVec(1f, 21.1f), p1, p2)
assertThat(triangle).isNotEqualTo(p0XChange)
assertThat(triangle).isNotEqualTo(p0YChange)
@@ -65,9 +68,9 @@
@Test
fun equals_whenP1different_returnsFalse() {
- val triangle = MutableTriangle(P0.newMutable(), MutableVec(3f, 4f), P2.newMutable())
- val p1XChange = MutableTriangle(P0.newMutable(), MutableVec(41.21f, 4f), P2.newMutable())
- val p1YChange = MutableTriangle(P0.newMutable(), MutableVec(3f, -6.77f), P2.newMutable())
+ val triangle = MutableTriangle(p0, MutableVec(3f, 4f), p2)
+ val p1XChange = MutableTriangle(p0, MutableVec(41.21f, 4f), p2)
+ val p1YChange = MutableTriangle(p0, MutableVec(3f, -6.77f), p2)
assertThat(triangle).isNotEqualTo(p1XChange)
assertThat(triangle).isNotEqualTo(p1YChange)
@@ -75,71 +78,17 @@
@Test
fun equals_whenP2different_returnsFalse() {
- val triangle = MutableTriangle(P0.newMutable(), P1.newMutable(), MutableVec(5f, 6f))
- val p2XChange = MutableTriangle(P0.newMutable(), P1.newMutable(), MutableVec(-0.43f, 6f))
- val p2YChange = MutableTriangle(P0.newMutable(), P1.newMutable(), MutableVec(5f, -10f))
+ val triangle = MutableTriangle(p0, p1, MutableVec(5f, 6f))
+ val p2XChange = MutableTriangle(p0, p1, MutableVec(-0.43f, 6f))
+ val p2YChange = MutableTriangle(p0, p1, MutableVec(5f, -10f))
assertThat(triangle).isNotEqualTo(p2XChange)
assertThat(triangle).isNotEqualTo(p2YChange)
}
@Test
- fun p0_correctlyModifiesP0Value() {
- val triangle = MutableTriangle(MutableVec(1f, 2f), P1.newMutable(), P2.newMutable())
-
- triangle.p0(MutableVec(1.5f, 21.6f))
-
- assertThat(triangle.p0).isEqualTo(MutableVec(1.5f, 21.6f))
- }
-
- @Test
- fun p0_withXYArgs_correctlyModifiesP0Value() {
- val triangle = MutableTriangle(MutableVec(1f, 2f), P1.newMutable(), P2.newMutable())
-
- triangle.p0(x = 1.5f, y = 21.6f)
-
- assertThat(triangle.p0).isEqualTo(MutableVec(1.5f, 21.6f))
- }
-
- @Test
- fun p1_correctlyModifiesP1Value() {
- val triangle = MutableTriangle(P0.newMutable(), MutableVec(3f, 4f), P2.newMutable())
-
- triangle.p1(MutableVec(20.9f, 513f))
-
- assertThat(triangle.p1).isEqualTo(MutableVec(20.9f, 513f))
- }
-
- @Test
- fun p1_withXYArgs_correctlyModifiesP1Value() {
- val triangle = MutableTriangle(P0.newMutable(), MutableVec(3f, 4f), P2.newMutable())
-
- triangle.p1(x = 20.9f, y = 513f)
-
- assertThat(triangle.p1).isEqualTo(MutableVec(20.9f, 513f))
- }
-
- @Test
- fun p2_correctlyModifiesP2Value() {
- val triangle = MutableTriangle(P0.newMutable(), P1.newMutable(), MutableVec(5f, 6f))
-
- triangle.p2(MutableVec(600f, 900f))
-
- assertThat(triangle.p2).isEqualTo(MutableVec(600f, 900f))
- }
-
- @Test
- fun p2_withXYArgs_correctlyModifiesP2Value() {
- val triangle = MutableTriangle(P0.newMutable(), P1.newMutable(), MutableVec(5f, 6f))
-
- triangle.p2(x = 600f, y = 900f)
-
- assertThat(triangle.p2).isEqualTo(MutableVec(600f, 900f))
- }
-
- @Test
fun populateFrom_correctlyCopiesValues() {
- val triangle = MutableTriangle(P0.newMutable(), P1.newMutable(), P2.newMutable())
+ val triangle = MutableTriangle(p0, p1, p2)
val other =
ImmutableTriangle(
ImmutableVec(10f, 11f),
@@ -156,7 +105,7 @@
@Test
fun contains_forContainedPoint_returnsTrue() {
- val triangle = MutableTriangle(P0, P1, P2)
+ val triangle = MutableTriangle(p0, p1, p2)
val point = MutableVec(4f, 3f)
assertThat(triangle.contains(point)).isTrue()
@@ -164,7 +113,7 @@
@Test
fun contains_forExternalPoint_returnsFalse() {
- val triangle = MutableTriangle(P0, P1, P2)
+ val triangle = MutableTriangle(p0, p1, p2)
val point = MutableVec(6f, 3f)
assertThat(triangle.contains(point)).isFalse()
@@ -172,73 +121,60 @@
@Test
fun edge_returnsCorrectSegment() {
- val triangle = MutableTriangle(P0, P1, P2)
+ val triangle = MutableTriangle(p0, p1, p2)
- assertThat(triangle.edge(0)).isEqualTo(MutableSegment(P0, P1))
- assertThat(triangle.edge(1)).isEqualTo(MutableSegment(P1, P2))
- assertThat(triangle.edge(2)).isEqualTo(MutableSegment(P2, P0))
- assertThat(triangle.edge(3)).isEqualTo(MutableSegment(P0, P1))
- assertThat(triangle.edge(4)).isEqualTo(MutableSegment(P1, P2))
- assertThat(triangle.edge(5)).isEqualTo(MutableSegment(P2, P0))
+ assertThat(triangle.computeEdge(0)).isEqualTo(MutableSegment(p0, p1))
+ assertThat(triangle.computeEdge(1)).isEqualTo(MutableSegment(p1, p2))
+ assertThat(triangle.computeEdge(2)).isEqualTo(MutableSegment(p2, p0))
+ assertThat(triangle.computeEdge(3)).isEqualTo(MutableSegment(p0, p1))
+ assertThat(triangle.computeEdge(4)).isEqualTo(MutableSegment(p1, p2))
+ assertThat(triangle.computeEdge(5)).isEqualTo(MutableSegment(p2, p0))
}
@Test
fun populateEdge_zeroIndex_correctlyPopulatesSegment() {
- val triangle = MutableTriangle(P0, P1, P2)
+ val triangle = MutableTriangle(p0, p1, p2)
val segment0 = MutableSegment()
val segment6 = MutableSegment()
- triangle.populateEdge(0, segment0)
- triangle.populateEdge(6, segment6)
+ triangle.computeEdge(0, segment0)
+ triangle.computeEdge(6, segment6)
- assertThat(segment0).isEqualTo(MutableSegment(P0, P1))
- assertThat(segment6).isEqualTo(MutableSegment(P0, P1))
+ assertThat(segment0).isEqualTo(MutableSegment(p0, p1))
+ assertThat(segment6).isEqualTo(MutableSegment(p0, p1))
}
@Test
fun populateEdge_oneIndex_correctlyPopulatesSegment() {
- val triangle = MutableTriangle(P0, P1, P2)
+ val triangle = MutableTriangle(p0, p1, p2)
val segment1 = MutableSegment()
val segment7 = MutableSegment()
- triangle.populateEdge(1, segment1)
- triangle.populateEdge(7, segment7)
+ triangle.computeEdge(1, segment1)
+ triangle.computeEdge(7, segment7)
- assertThat(segment1).isEqualTo(MutableSegment(P1, P2))
- assertThat(segment7).isEqualTo(MutableSegment(P1, P2))
+ assertThat(segment1).isEqualTo(MutableSegment(p1, p2))
+ assertThat(segment7).isEqualTo(MutableSegment(p1, p2))
}
@Test
fun populateEdge_twoIndex_correctlyPopulatesSegment() {
- val triangle = MutableTriangle(P0, P1, P2)
+ val triangle = MutableTriangle(p0, p1, p2)
val segment2 = MutableSegment()
val segment8 = MutableSegment()
- triangle.populateEdge(2, segment2)
- triangle.populateEdge(8, segment8)
+ triangle.computeEdge(2, segment2)
+ triangle.computeEdge(8, segment8)
- assertThat(segment2).isEqualTo(MutableSegment(P2, P0))
- assertThat(segment8).isEqualTo(MutableSegment(P2, P0))
+ assertThat(segment2).isEqualTo(MutableSegment(p2, p0))
+ assertThat(segment8).isEqualTo(MutableSegment(p2, p0))
}
@Test
fun asImmutable_returnsImmutableCopy() {
- val triangle = MutableTriangle(P0, P1, P2)
+ val triangle = MutableTriangle(p0, p1, p2)
val output = triangle.asImmutable()
- assertThat(output.p0).isEqualTo(P0)
- assertThat(output.p1).isEqualTo(P1)
- assertThat(output.p2).isEqualTo(P2)
- }
-
- @Test
- fun asImmutable_withNewValues_ReturnsNewImmutable() {
- val triangle = MutableTriangle(P0, P1, P2)
- val p0 = ImmutableVec(10f, 20f)
- val p1 = ImmutableVec(30f, 40f)
- val p2 = ImmutableVec(50f, 60f)
- val output = triangle.asImmutable(p0, p1, p2)
-
assertThat(output.p0).isEqualTo(p0)
assertThat(output.p1).isEqualTo(p1)
assertThat(output.p2).isEqualTo(p2)
@@ -260,7 +196,7 @@
@Test
fun toString_correctlyReturnsString() {
- val triangle = MutableTriangle(P0, P1, P2)
+ val triangle = MutableTriangle(p0, p1, p2)
val string = triangle.toString()
@@ -271,12 +207,4 @@
assertThat(string).contains("5")
assertThat(string).contains("6")
}
-
- companion object {
- private val P0 = ImmutableVec(1f, 2f)
-
- private val P1 = ImmutableVec(5f, 2f)
-
- private val P2 = ImmutableVec(5f, 6f)
- }
}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableVecTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableVecTest.kt
index afa1451..a4c4c52 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableVecTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/MutableVecTest.kt
@@ -36,9 +36,9 @@
@Test
fun equals_whenDifferentType_returnsFalse() {
val vec = MutableVec(1f, 2f)
- val point = MutablePoint(1f, 2f)
+ val segment = MutableSegment(MutableVec(1f, 2f), MutableVec(3f, 4f))
- assertThat(vec).isNotEqualTo(point)
+ assertThat(vec).isNotEqualTo(segment)
}
@Test
@@ -85,24 +85,6 @@
}
@Test
- fun x_modifiesValue() {
- val testVec = MutableVec(10f, 25f)
-
- testVec.x(999f)
-
- assertThat(testVec).isEqualTo(MutableVec(999f, 25f))
- }
-
- @Test
- fun y_modifiesValue() {
- val testVec = MutableVec(10f, 25f)
-
- testVec.y(999f)
-
- assertThat(testVec).isEqualTo(MutableVec(10f, 999f))
- }
-
- @Test
fun populateFrom_modifiesValue() {
val testVec = MutableVec(10f, 25f)
@@ -112,133 +94,126 @@
}
@Test
- fun orthogonal_returnsCorrectValue() {
- assertThat(MutableVec(3f, 1f).orthogonal).isEqualTo(ImmutableVec(-1f, 3f))
- assertThat(MutableVec(-395f, .005f).orthogonal).isEqualTo(ImmutableVec(-.005f, -395f))
- assertThat(MutableVec(-.2f, -.66f).orthogonal).isEqualTo(ImmutableVec(.66f, -.2f))
- assertThat(MutableVec(123f, -987f).orthogonal).isEqualTo(ImmutableVec(987f, 123f))
+ fun computeOrthogonal_returnsCorrectValue() {
+ assertThat(MutableVec(3f, 1f).computeOrthogonal()).isEqualTo(ImmutableVec(-1f, 3f))
+ assertThat(MutableVec(-395f, .005f).computeOrthogonal())
+ .isEqualTo(ImmutableVec(-.005f, -395f))
+ assertThat(MutableVec(-.2f, -.66f).computeOrthogonal()).isEqualTo(ImmutableVec(.66f, -.2f))
+ assertThat(MutableVec(123f, -987f).computeOrthogonal()).isEqualTo(ImmutableVec(987f, 123f))
}
@Test
- fun orthogonal_whenMutableVecIsModified_returnsCorrectValue() {
+ fun computeOrthogonal_whenMutableVecIsModified_returnsCorrectValue() {
val vec = MutableVec(3f, 1f)
- assertThat(vec.orthogonal).isEqualTo(ImmutableVec(-1f, 3f))
+ assertThat(vec.computeOrthogonal()).isEqualTo(ImmutableVec(-1f, 3f))
vec.x = 10f
vec.y = 2134f
- assertThat(vec.orthogonal).isEqualTo(ImmutableVec(-2134f, 10f))
+ assertThat(vec.computeOrthogonal()).isEqualTo(ImmutableVec(-2134f, 10f))
}
@Test
- fun populateOrthogonal_populatesCorrectValue() {
+ fun computeOrthogonal_populatesCorrectValue() {
val mutableVec = MutableVec()
- MutableVec(3f, 1f).populateOrthogonal(mutableVec)
+ MutableVec(3f, 1f).computeOrthogonal(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(-1f, 3f))
- MutableVec(-395f, .005f).populateOrthogonal(mutableVec)
+ MutableVec(-395f, .005f).computeOrthogonal(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(-.005f, -395f))
- MutableVec(-.2f, -.66f).populateOrthogonal(mutableVec)
+ MutableVec(-.2f, -.66f).computeOrthogonal(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(.66f, -.2f))
- MutableVec(123f, -987f).populateOrthogonal(mutableVec)
+ MutableVec(123f, -987f).computeOrthogonal(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(987f, 123f))
}
@Test
- fun populateOrthogonal_whenMutableVecIsModified_populatesCorrectValue() {
+ fun computeOrthogonal_whenMutableVecIsModified_populatesCorrectValue() {
val inputVec = MutableVec(3f, 1f)
val outputVec = MutableVec()
- inputVec.populateOrthogonal(outputVec)
+ inputVec.computeOrthogonal(outputVec)
assertThat(outputVec).isEqualTo(ImmutableVec(-1f, 3f))
inputVec.x = -9956f
inputVec.y = -.001f
- inputVec.populateOrthogonal(outputVec)
+ inputVec.computeOrthogonal(outputVec)
assertThat(outputVec).isEqualTo(ImmutableVec(.001f, -9956f))
}
@Test
- fun negation_returnsCorrectValue() {
- assertThat(MutableVec(3f, 1f).negation).isEqualTo(MutableVec(-3f, -1f))
- assertThat(MutableVec(-395f, .005f).negation).isEqualTo(MutableVec(395f, -.005f))
- assertThat(MutableVec(-.2f, -.66f).negation).isEqualTo(MutableVec(.2f, .66f))
- assertThat(MutableVec(123f, -987f).negation).isEqualTo(MutableVec(-123f, 987f))
+ fun computeNegation_returnsCorrectValue() {
+ assertThat(MutableVec(3f, 1f).computeNegation()).isEqualTo(MutableVec(-3f, -1f))
+ assertThat(MutableVec(-395f, .005f).computeNegation()).isEqualTo(MutableVec(395f, -.005f))
+ assertThat(MutableVec(-.2f, -.66f).computeNegation()).isEqualTo(MutableVec(.2f, .66f))
+ assertThat(MutableVec(123f, -987f).computeNegation()).isEqualTo(MutableVec(-123f, 987f))
}
@Test
- fun populateNegation_populatesCorrectValue() {
+ fun computeNegation_populatesCorrectValue() {
val mutableVec = MutableVec()
- MutableVec(3f, 1f).populateNegation(mutableVec)
+ MutableVec(3f, 1f).computeNegation(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(-3f, -1f))
- MutableVec(-395f, .005f).populateNegation(mutableVec)
+ MutableVec(-395f, .005f).computeNegation(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(395f, -.005f))
- MutableVec(-.2f, -.66f).populateNegation(mutableVec)
+ MutableVec(-.2f, -.66f).computeNegation(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(.2f, .66f))
- MutableVec(123f, -987f).populateNegation(mutableVec)
+ MutableVec(123f, -987f).computeNegation(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(-123f, 987f))
}
@Test
- fun negation_whenMutableVecIsModified_returnsCorrectValue() {
+ fun computeNegation_whenMutableVecIsModified_returnsCorrectValue() {
val vec = MutableVec(3f, 1f)
- assertThat(vec.negation).isEqualTo(MutableVec(-3f, -1f))
+ assertThat(vec.computeNegation()).isEqualTo(MutableVec(-3f, -1f))
vec.x = 10f
vec.y = 2134f
- assertThat(vec.negation).isEqualTo(MutableVec(-10f, -2134f))
+ assertThat(vec.computeNegation()).isEqualTo(MutableVec(-10f, -2134f))
}
@Test
- fun populateNegation_whenMutableVecIsModified_populatesCorrectValue() {
+ fun computeNegation_whenMutableVecIsModified_populatesCorrectValue() {
val inputVec = MutableVec(3f, 1f)
val outputVec = MutableVec()
- inputVec.populateNegation(outputVec)
+ inputVec.computeNegation(outputVec)
assertThat(outputVec).isEqualTo(MutableVec(-3f, -1f))
inputVec.x = -9956f
inputVec.y = -.001f
- inputVec.populateNegation(outputVec)
+ inputVec.computeNegation(outputVec)
assertThat(outputVec).isEqualTo(MutableVec(9956f, .001f))
}
@Test
- fun magnitude_returnsCorrectValue() {
- assertThat(MutableVec(1f, 1f).magnitude).isEqualTo(sqrt(2f))
- assertThat(MutableVec(-3f, 4f).magnitude).isEqualTo(5f)
- assertThat(MutableVec(0f, 0f).magnitude).isEqualTo(0f)
- assertThat(MutableVec(0f, 17f).magnitude).isEqualTo(17f)
+ fun computeMagnitude_returnsCorrectValue() {
+ assertThat(MutableVec(1f, 1f).computeMagnitude()).isEqualTo(sqrt(2f))
+ assertThat(MutableVec(-3f, 4f).computeMagnitude()).isEqualTo(5f)
+ assertThat(MutableVec(0f, 0f).computeMagnitude()).isEqualTo(0f)
+ assertThat(MutableVec(0f, 17f).computeMagnitude()).isEqualTo(17f)
}
@Test
- fun magnitude_whenMutableVecIsModified_returnsCorrectValue() {
+ fun computeMagnitude_whenMutableVecIsModified_returnsCorrectValue() {
val vec = MutableVec(-3f, 4f)
- assertThat(vec.magnitude).isEqualTo(5f)
+ assertThat(vec.computeMagnitude()).isEqualTo(5f)
vec.x = 5f
vec.y = 12f
- assertThat(vec.magnitude).isEqualTo(13f)
+ assertThat(vec.computeMagnitude()).isEqualTo(13f)
}
@Test
- fun magnitudeSquared_returnsCorrectValue() {
- assertThat(MutableVec(1f, 1f).magnitudeSquared).isEqualTo(2f)
- assertThat(MutableVec(3f, -4f).magnitudeSquared).isEqualTo(25f)
- assertThat(MutableVec(0f, 0f).magnitudeSquared).isEqualTo(0f)
- assertThat(MutableVec(15f, 0f).magnitudeSquared).isEqualTo(225f)
+ fun computeMagnitudeSquared_returnsCorrectValue() {
+ assertThat(MutableVec(1f, 1f).computeMagnitudeSquared()).isEqualTo(2f)
+ assertThat(MutableVec(3f, -4f).computeMagnitudeSquared()).isEqualTo(25f)
+ assertThat(MutableVec(0f, 0f).computeMagnitudeSquared()).isEqualTo(0f)
+ assertThat(MutableVec(15f, 0f).computeMagnitudeSquared()).isEqualTo(225f)
}
@Test
- fun magnitudeSquared_whenMutableVecIsModified_returnsCorrectValue() {
+ fun computeMagnitudeSquared_whenMutableVecIsModified_returnsCorrectValue() {
val vec = MutableVec(-3f, 4f)
- assertThat(vec.magnitudeSquared).isEqualTo(25f)
+ assertThat(vec.computeMagnitudeSquared()).isEqualTo(25f)
vec.x = 5f
vec.y = 12f
- assertThat(vec.magnitudeSquared).isEqualTo(169f)
+ assertThat(vec.computeMagnitudeSquared()).isEqualTo(169f)
}
@Test
- fun asImmutableVal_returnsNewEquivalentImmutableVec() {
- val vec = MutableVec(1f, 2f)
-
- assertThat(vec.asImmutable).isNotSameInstanceAs(vec)
- assertThat(vec.asImmutable).isEqualTo(vec)
- }
-
- @Test
- fun asImmutableFun_withNoArguments_returnsNewEquivalentImmutableVec() {
+ fun asImmutable_returnsNewEquivalentImmutableVec() {
val vec = MutableVec(1f, 2f)
assertThat(vec.asImmutable()).isNotSameInstanceAs(vec)
@@ -246,23 +221,12 @@
}
@Test
- fun asImmutableFun_withArguments_returnsCorrectNewImmutableVec() {
- val vec = MutableVec(1f, 2f)
-
- assertThat(vec.asImmutable(x = 10f)).isEqualTo(ImmutableVec(10f, 2f))
- assertThat(vec.asImmutable(10f)).isEqualTo(ImmutableVec(10f, 2f))
- assertThat(vec.asImmutable(y = 20f)).isEqualTo(ImmutableVec(1f, 20f))
- assertThat(vec.asImmutable(x = 10f, y = 20f)).isEqualTo(ImmutableVec(10f, 20f))
- assertThat(vec.asImmutable(10f, 20f)).isEqualTo(ImmutableVec(10f, 20f))
- }
-
- @Test
- fun unitVec_whenModified_returnsCorrectValue() {
+ fun computeUnitVec_whenModified_returnsCorrectValue() {
val vec = MutableVec(4f, 0f)
- assertThat(vec.unitVec).isEqualTo(ImmutableVec(1f, 0f))
+ assertThat(vec.computeUnitVec()).isEqualTo(ImmutableVec(1f, 0f))
vec.x = 0f
vec.y = -.05f
- assertThat(vec.unitVec).isEqualTo(ImmutableVec(0f, -1f))
+ assertThat(vec.computeUnitVec()).isEqualTo(ImmutableVec(0f, -1f))
}
@Test
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ParallelogramInterfaceTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ParallelogramInterfaceTest.kt
index f9982d6..f4e8ead 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ParallelogramInterfaceTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/ParallelogramInterfaceTest.kt
@@ -29,13 +29,13 @@
val expectedWidth = 5f
val expectedHeight = -3f
val expectedRotation = Angle.QUARTER_TURN_RADIANS + Angle.HALF_TURN_RADIANS
- val assertExpectedValues: (Float, Float, Float) -> TestParallelogram =
+ val assertExpectedValues: (Float, Float, Float) -> Parallelogram =
{ normalizedWidth: Float, normalizedHeight: Float, normalizedRotation: Float ->
assertThat(normalizedWidth).isEqualTo(expectedWidth)
assertThat(normalizedHeight).isEqualTo(expectedHeight)
assertThat(normalizedRotation).isWithin(tolerance).of(expectedRotation)
- TestParallelogram(
- ImmutablePoint(0f, 0f),
+ ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
+ ImmutableVec(0f, 0f),
expectedWidth,
expectedHeight,
expectedRotation,
@@ -55,13 +55,13 @@
val expectedWidth = 5f
val expectedHeight = 3f
val expectedRotation = Angle.QUARTER_TURN_RADIANS // 5 Pi normalized to range [0, 2*pi]
- val assertExpectedValues: (Float, Float, Float) -> TestParallelogram =
+ val assertExpectedValues: (Float, Float, Float) -> Parallelogram =
{ normalizedWidth: Float, normalizedHeight: Float, normalizedRotation: Float ->
assertThat(normalizedWidth).isEqualTo(expectedWidth)
assertThat(normalizedHeight).isEqualTo(expectedHeight)
assertThat(normalizedRotation).isWithin(tolerance).of(expectedRotation)
- TestParallelogram(
- ImmutablePoint(0f, 0f),
+ ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
+ ImmutableVec(0f, 0f),
expectedWidth,
expectedHeight,
expectedRotation,
@@ -84,23 +84,17 @@
width = 5f,
height = 3f,
rotation = Angle.QUARTER_TURN_RADIANS,
- runBlock = TestParallelogram.makeTestParallelogram,
+ runBlock = { w: Float, h: Float, r: Float ->
+ ImmutableParallelogram.fromCenterDimensionsRotationAndShear(
+ ImmutableVec(0f, 0f),
+ w,
+ h,
+ r,
+ 0f,
+ )
+ },
)
- assertThat(parallelogram.signedArea()).isEqualTo(15f)
- }
-
- private class TestParallelogram(
- override val center: ImmutablePoint,
- override val width: Float,
- override val height: Float,
- override val rotation: Float,
- override val shearFactor: Float,
- ) : Parallelogram {
- companion object {
- val makeTestParallelogram = { w: Float, h: Float, r: Float ->
- TestParallelogram(ImmutablePoint(0f, 0f), w, h, r, 0f)
- }
- }
+ assertThat(parallelogram.computeSignedArea()).isEqualTo(15f)
}
private val tolerance = 0.000001f
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/PartitionedMeshTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/PartitionedMeshTest.kt
new file mode 100644
index 0000000..682b88d
--- /dev/null
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/PartitionedMeshTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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.ink.geometry
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class PartitionedMeshTest {
+
+ @Test
+ fun bounds_shouldBeEmpty() {
+ val partitionedMesh = PartitionedMesh()
+
+ assertThat(partitionedMesh.bounds).isNull()
+ }
+
+ @Test
+ fun renderGroupCount_whenEmptyShape_shouldBeZero() {
+ val partitionedMesh = PartitionedMesh()
+
+ assertThat(partitionedMesh.renderGroupCount).isEqualTo(0)
+ }
+
+ @Test
+ fun outlineCount_whenEmptyShape_shouldThrow() {
+ val partitionedMesh = PartitionedMesh()
+
+ assertFailsWith<IllegalArgumentException> { partitionedMesh.outlineCount(-1) }
+ assertFailsWith<IllegalArgumentException> { partitionedMesh.outlineCount(0) }
+ assertFailsWith<IllegalArgumentException> { partitionedMesh.outlineCount(1) }
+ }
+
+ @Test
+ fun outlineVertexCount_whenEmptyShape_shouldThrow() {
+ val partitionedMesh = PartitionedMesh()
+
+ assertFailsWith<IllegalArgumentException> { partitionedMesh.outlineVertexCount(-1, 0) }
+ assertFailsWith<IllegalArgumentException> { partitionedMesh.outlineVertexCount(0, 0) }
+ assertFailsWith<IllegalArgumentException> { partitionedMesh.outlineVertexCount(1, 0) }
+ }
+
+ @Test
+ fun populateOutlinePosition_whenEmptyShape_shouldThrow() {
+ val partitionedMesh = PartitionedMesh()
+
+ assertFailsWith<IllegalArgumentException> {
+ partitionedMesh.populateOutlinePosition(-1, 0, 0, MutableVec())
+ }
+ assertFailsWith<IllegalArgumentException> {
+ partitionedMesh.populateOutlinePosition(0, 0, 0, MutableVec())
+ }
+ assertFailsWith<IllegalArgumentException> {
+ partitionedMesh.populateOutlinePosition(1, 0, 0, MutableVec())
+ }
+ }
+
+ @Test
+ fun toString_returnsAString() {
+ val string = PartitionedMesh().toString()
+
+ // Not elaborate checks - this test mainly exists to ensure that toString doesn't crash.
+ assertThat(string).contains("PartitionedMesh")
+ assertThat(string).contains("bounds")
+ assertThat(string).contains("meshes")
+ assertThat(string).contains("nativeAddress")
+ }
+}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/PointTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/PointTest.kt
deleted file mode 100644
index e6e4eef..0000000
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/PointTest.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2024 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.ink.geometry
-
-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 PointTest {
-
- @Test
- fun isAlmostEqual_whenNoToleranceGiven_returnsCorrectValue() {
- val point = ImmutablePoint(1f, 2f)
-
- assertThat(point.isAlmostEqual(point)).isTrue()
- assertThat(point.isAlmostEqual(ImmutablePoint(1f, 2f))).isTrue()
- assertThat(point.isAlmostEqual(ImmutablePoint(1.00001f, 1.99999f))).isTrue()
- assertThat(point.isAlmostEqual(ImmutablePoint(1f, 1.99f))).isFalse()
- assertThat(point.isAlmostEqual(ImmutablePoint(1.01f, 2f))).isFalse()
- assertThat(point.isAlmostEqual(ImmutablePoint(1.01f, 1.99f))).isFalse()
- }
-
- @Test
- fun isAlmostEqual_withToleranceGiven_returnsCorrectValue() {
- val point = ImmutablePoint(1f, 2f)
-
- assertThat(point.isAlmostEqual(point, tolerance = 0.00000001f)).isTrue()
- assertThat(point.isAlmostEqual(ImmutablePoint(1f, 2f), tolerance = 0.00000001f)).isTrue()
- assertThat(point.isAlmostEqual(ImmutablePoint(1.00001f, 1.99999f), tolerance = 0.000001f))
- .isFalse()
- assertThat(point.isAlmostEqual(ImmutablePoint(1f, 1.99f), tolerance = 0.02f)).isTrue()
- assertThat(point.isAlmostEqual(ImmutablePoint(1.01f, 2f), tolerance = 0.02f)).isTrue()
- assertThat(point.isAlmostEqual(ImmutablePoint(1.01f, 1.99f), tolerance = 0.02f)).isTrue()
- assertThat(point.isAlmostEqual(ImmutablePoint(2.5f, 0.5f), tolerance = 2f)).isTrue()
- }
-
- @Test
- fun isAlmostEqual_whenSameInterface_returnsTrue() {
- val point = MutablePoint(1f, 2f)
- val other = ImmutablePoint(0.99999f, 2.00001f)
- assertThat(point.isAlmostEqual(other)).isTrue()
- }
-}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/SegmentTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/SegmentTest.kt
index 48403e1..ed453d4 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/SegmentTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/SegmentTest.kt
@@ -28,37 +28,37 @@
@Test
fun length_returnsCorrectValue() {
- assertThat(ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(1f, 1f)).length)
+ assertThat(ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(1f, 1f)).computeLength())
.isEqualTo(sqrt(2f))
- assertThat(ImmutableSegment(ImmutableVec(-4f, 2f), ImmutableVec(0f, 5f)).length)
+ assertThat(ImmutableSegment(ImmutableVec(-4f, 2f), ImmutableVec(0f, 5f)).computeLength())
.isEqualTo(5f)
- assertThat(ImmutableSegment(ImmutableVec(0f, 1f), ImmutableVec(-1f, 3f)).length)
+ assertThat(ImmutableSegment(ImmutableVec(0f, 1f), ImmutableVec(-1f, 3f)).computeLength())
.isEqualTo(sqrt(5f))
- assertThat(ImmutableSegment(ImmutableVec(3f, 4f), ImmutableVec(-1f, -1f)).length)
+ assertThat(ImmutableSegment(ImmutableVec(3f, 4f), ImmutableVec(-1f, -1f)).computeLength())
.isEqualTo(sqrt(41f))
}
@Test
fun length_whenSegmentIsHorizontal_returnsCorrectValue() {
- assertThat(ImmutableSegment(ImmutableVec(1f, 1f), ImmutableVec(1f, -3f)).length)
+ assertThat(ImmutableSegment(ImmutableVec(1f, 1f), ImmutableVec(1f, -3f)).computeLength())
.isEqualTo(4f)
- assertThat(ImmutableSegment(ImmutableVec(3f, -2f), ImmutableVec(3f, 4f)).length)
+ assertThat(ImmutableSegment(ImmutableVec(3f, -2f), ImmutableVec(3f, 4f)).computeLength())
.isEqualTo(6f)
}
@Test
fun length_whenSegmentIsVertical_returnsCorrectValue() {
- assertThat(ImmutableSegment(ImmutableVec(4f, 1f), ImmutableVec(5f, 1f)).length)
+ assertThat(ImmutableSegment(ImmutableVec(4f, 1f), ImmutableVec(5f, 1f)).computeLength())
.isEqualTo(1f)
- assertThat(ImmutableSegment(ImmutableVec(-1f, -5f), ImmutableVec(-3f, -5f)).length)
+ assertThat(ImmutableSegment(ImmutableVec(-1f, -5f), ImmutableVec(-3f, -5f)).computeLength())
.isEqualTo(2f)
}
@Test
fun length_whenSegmentIsDegenerate_returnsZero() {
- assertThat(ImmutableSegment(ImmutableVec(4f, 1f), ImmutableVec(4f, 1f)).length)
+ assertThat(ImmutableSegment(ImmutableVec(4f, 1f), ImmutableVec(4f, 1f)).computeLength())
.isEqualTo(0f)
- assertThat(ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f)).length)
+ assertThat(ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f)).computeLength())
.isEqualTo(0f)
}
@@ -66,35 +66,35 @@
fun vec_fillsCorrectValues() {
assertThat(
ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(1f, 1f))
- .vec
+ .computeDisplacement()
.isAlmostEqual(ImmutableVec(1f, 1f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(-4f, 2f), ImmutableVec(0f, 5f))
- .vec
+ .computeDisplacement()
.isAlmostEqual(ImmutableVec(4f, 3f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(0f, 1f), ImmutableVec(-1f, 3f))
- .vec
+ .computeDisplacement()
.isAlmostEqual(ImmutableVec(-1f, 2f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(3f, 4f), ImmutableVec(-1f, -1f))
- .vec
+ .computeDisplacement()
.isAlmostEqual(ImmutableVec(-4f, -5f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(0.6f, 1.9f), ImmutableVec(-1.2f, 3.3f))
- .vec
+ .computeDisplacement()
.isAlmostEqual(ImmutableVec(-1.8f, 1.4f), 0.000001f)
)
.isTrue()
@@ -104,14 +104,14 @@
fun vec_whenSegmentIsHorizontal_fillsCorrectValues() {
assertThat(
ImmutableSegment(ImmutableVec(1f, 1f), ImmutableVec(1f, -3f))
- .vec
+ .computeDisplacement()
.isAlmostEqual(ImmutableVec(0f, -4f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(3f, -2f), ImmutableVec(3f, 4f))
- .vec
+ .computeDisplacement()
.isAlmostEqual(ImmutableVec(0f, 6f), 0.000001f)
)
.isTrue()
@@ -121,14 +121,14 @@
fun vec_whenSegmentIsVertical_fillsCorrectValues() {
assertThat(
ImmutableSegment(ImmutableVec(4f, 1f), ImmutableVec(5f, 1f))
- .vec
+ .computeDisplacement()
.isAlmostEqual(ImmutableVec(1f, 0f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(-1f, -5f), ImmutableVec(-3f, -5f))
- .vec
+ .computeDisplacement()
.isAlmostEqual(ImmutableVec(-2f, 0f), 0.000001f)
)
.isTrue()
@@ -138,14 +138,14 @@
fun vec_whenSegmentIsDegenerate_fillsZeroes() {
assertThat(
ImmutableSegment(ImmutableVec(1f, -5f), ImmutableVec(1f, -5f))
- .vec
+ .computeDisplacement()
.isAlmostEqual(ImmutableVec(0f, 0f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f))
- .vec
+ .computeDisplacement()
.isAlmostEqual(ImmutableVec(0f, 0f), 0.000001f)
)
.isTrue()
@@ -154,50 +154,57 @@
@Test
fun populateVec_fillsCorrectValues() {
val mutableVec = MutableVec(0f, 0f)
- ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(1f, 1f)).populateVec(mutableVec)
+ ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(1f, 1f)).computeDisplacement(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(1f, 1f))
- ImmutableSegment(ImmutableVec(-4f, 2f), ImmutableVec(0f, 5f)).populateVec(mutableVec)
+ ImmutableSegment(ImmutableVec(-4f, 2f), ImmutableVec(0f, 5f))
+ .computeDisplacement(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(4f, 3f))
- ImmutableSegment(ImmutableVec(0f, 1f), ImmutableVec(-1f, 3f)).populateVec(mutableVec)
+ ImmutableSegment(ImmutableVec(0f, 1f), ImmutableVec(-1f, 3f))
+ .computeDisplacement(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(-1f, 2f))
- ImmutableSegment(ImmutableVec(3f, 4f), ImmutableVec(-1f, -1f)).populateVec(mutableVec)
+ ImmutableSegment(ImmutableVec(3f, 4f), ImmutableVec(-1f, -1f))
+ .computeDisplacement(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(-4f, -5f))
ImmutableSegment(ImmutableVec(0.6f, 1.9f), ImmutableVec(-1.2f, 3.3f))
- .populateVec(mutableVec)
+ .computeDisplacement(mutableVec)
assertThat(mutableVec.isAlmostEqual(MutableVec(-1.8f, 1.4f), 0.000001f)).isTrue()
}
@Test
fun populateVec_whenSegmentIsHorizontal_fillsCorrectValues() {
val mutableVec = MutableVec(0f, 0f)
- ImmutableSegment(ImmutableVec(1f, 1f), ImmutableVec(1f, -3f)).populateVec(mutableVec)
+ ImmutableSegment(ImmutableVec(1f, 1f), ImmutableVec(1f, -3f))
+ .computeDisplacement(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(0f, -4f))
- ImmutableSegment(ImmutableVec(3f, -2f), ImmutableVec(3f, 4f)).populateVec(mutableVec)
+ ImmutableSegment(ImmutableVec(3f, -2f), ImmutableVec(3f, 4f))
+ .computeDisplacement(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(0f, 6f))
}
@Test
fun populateVec_whenSegmentIsVertical_fillsCorrectValues() {
val mutableVec = MutableVec(0f, 0f)
- ImmutableSegment(ImmutableVec(4f, 1f), ImmutableVec(5f, 1f)).populateVec(mutableVec)
+ ImmutableSegment(ImmutableVec(4f, 1f), ImmutableVec(5f, 1f)).computeDisplacement(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(1f, 0f))
- ImmutableSegment(ImmutableVec(-1f, -5f), ImmutableVec(-3f, -5f)).populateVec(mutableVec)
+ ImmutableSegment(ImmutableVec(-1f, -5f), ImmutableVec(-3f, -5f))
+ .computeDisplacement(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(-2f, 0f))
}
@Test
fun populateVec_whenSegmentIsDegenerate_fillsZeroes() {
val mutableVec = MutableVec(0f, 0f)
- ImmutableSegment(ImmutableVec(1f, -5f), ImmutableVec(1f, -5f)).populateVec(mutableVec)
+ ImmutableSegment(ImmutableVec(1f, -5f), ImmutableVec(1f, -5f))
+ .computeDisplacement(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(0f, 0f))
- ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f)).populateVec(mutableVec)
+ ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f)).computeDisplacement(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(0f, 0f))
}
@@ -205,35 +212,35 @@
fun midpoint_fillsCorrectValues() {
assertThat(
ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(1f, 1f))
- .midpoint
+ .computeMidpoint()
.isAlmostEqual(ImmutableVec(.5f, .5f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(-4f, 2f), ImmutableVec(0f, 5f))
- .midpoint
+ .computeMidpoint()
.isAlmostEqual(ImmutableVec(-2f, 3.5f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(0f, 1f), ImmutableVec(-1f, 3f))
- .midpoint
+ .computeMidpoint()
.isAlmostEqual(ImmutableVec(-.5f, 2f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(3f, 4f), ImmutableVec(-1f, -1f))
- .midpoint
+ .computeMidpoint()
.isAlmostEqual(ImmutableVec(1f, 1.5f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(0.6f, 1.9f), ImmutableVec(-1.2f, 3.3f))
- .midpoint
+ .computeMidpoint()
.isAlmostEqual(ImmutableVec(-.3f, 2.6f), 0.000001f)
)
.isTrue()
@@ -243,14 +250,14 @@
fun midpoint_whenSegmentIsHorizontal_fillsCorrectValues() {
assertThat(
ImmutableSegment(ImmutableVec(1f, 1f), ImmutableVec(1f, -3f))
- .midpoint
+ .computeMidpoint()
.isAlmostEqual(ImmutableVec(1f, -1f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(3f, -2f), ImmutableVec(3f, 4f))
- .midpoint
+ .computeMidpoint()
.isAlmostEqual(ImmutableVec(3f, 1f), 0.000001f)
)
.isTrue()
@@ -260,14 +267,14 @@
fun midpoint_whenSegmentIsVertical_fillsCorrectValues() {
assertThat(
ImmutableSegment(ImmutableVec(4f, 1f), ImmutableVec(5f, 1f))
- .midpoint
+ .computeMidpoint()
.isAlmostEqual(ImmutableVec(4.5f, 1f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(-1f, -5f), ImmutableVec(-3f, -5f))
- .midpoint
+ .computeMidpoint()
.isAlmostEqual(ImmutableVec(-2f, -5f), 0.000001f)
)
.isTrue()
@@ -277,14 +284,14 @@
fun midpoint_whenSegmentIsDegenerate_fillsZeroes() {
assertThat(
ImmutableSegment(ImmutableVec(1f, -5f), ImmutableVec(1f, -5f))
- .midpoint
+ .computeMidpoint()
.isAlmostEqual(ImmutableVec(1f, -5f), 0.000001f)
)
.isTrue()
assertThat(
ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f))
- .midpoint
+ .computeMidpoint()
.isAlmostEqual(ImmutableVec(0f, 0f), 0.000001f)
)
.isTrue()
@@ -293,113 +300,112 @@
@Test
fun populateMidpoint_fillsCorrectValues() {
val mutableVec = MutableVec(0f, 0f)
- ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(1f, 1f)).populateMidpoint(mutableVec)
+ ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(1f, 1f)).computeMidpoint(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(.5f, .5f))
- ImmutableSegment(ImmutableVec(-4f, 2f), ImmutableVec(0f, 5f)).populateMidpoint(mutableVec)
+ ImmutableSegment(ImmutableVec(-4f, 2f), ImmutableVec(0f, 5f)).computeMidpoint(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(-2f, 3.5f))
- ImmutableSegment(ImmutableVec(0f, 1f), ImmutableVec(-1f, 3f)).populateMidpoint(mutableVec)
+ ImmutableSegment(ImmutableVec(0f, 1f), ImmutableVec(-1f, 3f)).computeMidpoint(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(-.5f, 2f))
- ImmutableSegment(ImmutableVec(3f, 4f), ImmutableVec(-1f, -1f)).populateMidpoint(mutableVec)
+ ImmutableSegment(ImmutableVec(3f, 4f), ImmutableVec(-1f, -1f)).computeMidpoint(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(1f, 1.5f))
ImmutableSegment(ImmutableVec(0.6f, 1.9f), ImmutableVec(-1.2f, 3.3f))
- .populateMidpoint(mutableVec)
+ .computeMidpoint(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(-.3f, 2.6f))
}
@Test
fun populateMidpoint_whenSegmentIsHorizontal_fillsCorrectValues() {
val mutableVec = MutableVec(0f, 0f)
- ImmutableSegment(ImmutableVec(1f, 1f), ImmutableVec(1f, -3f)).populateMidpoint(mutableVec)
+ ImmutableSegment(ImmutableVec(1f, 1f), ImmutableVec(1f, -3f)).computeMidpoint(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(1f, -1f))
- ImmutableSegment(ImmutableVec(3f, -2f), ImmutableVec(3f, 4f)).populateMidpoint(mutableVec)
+ ImmutableSegment(ImmutableVec(3f, -2f), ImmutableVec(3f, 4f)).computeMidpoint(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(3f, 1f))
}
@Test
fun populateMidpoint_whenSegmentIsVertical_fillsCorrectValues() {
val mutableVec = MutableVec(0f, 0f)
- ImmutableSegment(ImmutableVec(4f, 1f), ImmutableVec(5f, 1f)).populateMidpoint(mutableVec)
+ ImmutableSegment(ImmutableVec(4f, 1f), ImmutableVec(5f, 1f)).computeMidpoint(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(4.5f, 1f))
- ImmutableSegment(ImmutableVec(-1f, -5f), ImmutableVec(-3f, -5f))
- .populateMidpoint(mutableVec)
+ ImmutableSegment(ImmutableVec(-1f, -5f), ImmutableVec(-3f, -5f)).computeMidpoint(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(-2f, -5f))
}
@Test
fun populateMidpoint_whenSegmentIsDegenerate_fillsZeroes() {
val mutableVec = MutableVec(0f, 0f)
- ImmutableSegment(ImmutableVec(1f, -5f), ImmutableVec(1f, -5f)).populateMidpoint(mutableVec)
+ ImmutableSegment(ImmutableVec(1f, -5f), ImmutableVec(1f, -5f)).computeMidpoint(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(1f, -5f))
- ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f)).populateMidpoint(mutableVec)
+ ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f)).computeMidpoint(mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(0f, 0f))
}
@Test
fun boundingBox_correctlyReturnsBoundingBox() {
- val segment0 = MutableSegment(ImmutableVec(1f, 1f), ImmutableVec(5f, 2f))
+ val segment0 = MutableSegment(MutableVec(1f, 1f), MutableVec(5f, 2f))
val segment1 = ImmutableSegment(ImmutableVec(-1f, 2f), ImmutableVec(0f, 0f))
- assertThat(segment0.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(1f, 1f), ImmutablePoint(5f, 2f)))
- assertThat(segment1.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(-1f, 0f), ImmutablePoint(0f, 2f)))
+ assertThat(segment0.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(1f, 1f), ImmutableVec(5f, 2f)))
+ assertThat(segment1.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(-1f, 0f), ImmutableVec(0f, 2f)))
}
@Test
fun boundingBox_forDegenerateSegment_correctlyReturnsBoundingBox() {
- val segment0 = MutableSegment(ImmutableVec(3f, 2f), ImmutableVec(3f, 2f))
+ val segment0 = MutableSegment(MutableVec(3f, 2f), MutableVec(3f, 2f))
val segment1 = ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f))
- assertThat(segment0.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(3f, 2f), ImmutablePoint(3f, 2f)))
- assertThat(segment1.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(0f, 0f), ImmutablePoint(0f, 0f)))
+ assertThat(segment0.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(3f, 2f), ImmutableVec(3f, 2f)))
+ assertThat(segment1.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f)))
}
@Test
fun populateBoundingBox_correctlyReturnsBoundingBox() {
- val segment0 = MutableSegment(ImmutableVec(1f, 1f), ImmutableVec(5f, 2f))
+ val segment0 = MutableSegment(MutableVec(1f, 1f), MutableVec(5f, 2f))
val segment1 = ImmutableSegment(ImmutableVec(-1f, 2f), ImmutableVec(0f, 0f))
val box0 = MutableBox()
val box1 = MutableBox()
- segment0.populateBoundingBox(box0)
- segment1.populateBoundingBox(box1)
+ segment0.computeBoundingBox(box0)
+ segment1.computeBoundingBox(box1)
assertThat(box0)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(1f, 1f), ImmutablePoint(5f, 2f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(1f, 1f), ImmutableVec(5f, 2f))
)
assertThat(box1)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(-1f, 0f), ImmutablePoint(0f, 2f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(-1f, 0f), ImmutableVec(0f, 2f))
)
}
@Test
fun populateBoundingBox_forDegenerateSegment_correctlyReturnsBoundingBox() {
- val segment0 = MutableSegment(ImmutableVec(3f, 2f), ImmutableVec(3f, 2f))
+ val segment0 = MutableSegment(MutableVec(3f, 2f), MutableVec(3f, 2f))
val segment1 = ImmutableSegment(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f))
val box0 = MutableBox()
val box1 = MutableBox()
- segment0.populateBoundingBox(box0)
- segment1.populateBoundingBox(box1)
+ segment0.computeBoundingBox(box0)
+ segment1.computeBoundingBox(box1)
assertThat(box0)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(3f, 2f), ImmutablePoint(3f, 2f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(3f, 2f), ImmutableVec(3f, 2f))
)
assertThat(box1)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(0f, 0f), ImmutablePoint(0f, 0f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(0f, 0f), ImmutableVec(0f, 0f))
)
}
@@ -407,21 +413,28 @@
fun lerpPoint_withZeroOrOneRatio_fillsCorrectValues() {
val segment = ImmutableSegment(ImmutableVec(6f, 3f), ImmutableVec(8f, -5f))
- assertThat(segment.lerpPoint(0.0f).isAlmostEqual(ImmutableVec(6f, 3f), 0.000001f)).isTrue()
+ assertThat(segment.computeLerpPoint(0.0f).isAlmostEqual(ImmutableVec(6f, 3f), 0.000001f))
+ .isTrue()
- assertThat(segment.lerpPoint(1.0f).isAlmostEqual(ImmutableVec(8f, -5f), 0.000001f)).isTrue()
+ assertThat(segment.computeLerpPoint(1.0f).isAlmostEqual(ImmutableVec(8f, -5f), 0.000001f))
+ .isTrue()
}
@Test
fun lerpPoint_withRatioBetweenZeroAndOne_fillsCorrectValues() {
val segment = ImmutableSegment(ImmutableVec(6f, 3f), ImmutableVec(8f, -5f))
- assertThat(segment.lerpPoint(0.2f).isAlmostEqual(ImmutableVec(6.4f, 1.4f), 0.000001f))
+ assertThat(
+ segment.computeLerpPoint(0.2f).isAlmostEqual(ImmutableVec(6.4f, 1.4f), 0.000001f)
+ )
.isTrue()
- assertThat(segment.lerpPoint(0.5f).isAlmostEqual(ImmutableVec(7f, -1f), 0.000001f)).isTrue()
+ assertThat(segment.computeLerpPoint(0.5f).isAlmostEqual(ImmutableVec(7f, -1f), 0.000001f))
+ .isTrue()
- assertThat(segment.lerpPoint(0.9f).isAlmostEqual(ImmutableVec(7.8f, -4.2f), 0.000001f))
+ assertThat(
+ segment.computeLerpPoint(0.9f).isAlmostEqual(ImmutableVec(7.8f, -4.2f), 0.000001f)
+ )
.isTrue()
}
@@ -429,9 +442,12 @@
fun lerpPoint_withRatioOutsideZeroAndOne_fillsCorrectValues() {
val segment = ImmutableSegment(ImmutableVec(6f, 3f), ImmutableVec(8f, -5f))
- assertThat(segment.lerpPoint(-1f).isAlmostEqual(ImmutableVec(4f, 11f), 0.000001f)).isTrue()
+ assertThat(segment.computeLerpPoint(-1f).isAlmostEqual(ImmutableVec(4f, 11f), 0.000001f))
+ .isTrue()
- assertThat(segment.lerpPoint(1.3f).isAlmostEqual(ImmutableVec(8.6f, -7.4f), 0.000001f))
+ assertThat(
+ segment.computeLerpPoint(1.3f).isAlmostEqual(ImmutableVec(8.6f, -7.4f), 0.000001f)
+ )
.isTrue()
}
@@ -440,10 +456,10 @@
val segment = ImmutableSegment(ImmutableVec(6f, 3f), ImmutableVec(8f, -5f))
val mutableVec = MutableVec(0f, 0f)
- segment.populateLerpPoint(0.0f, mutableVec)
+ segment.computeLerpPoint(0.0f, mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(6f, 3f))
- segment.populateLerpPoint(1.0f, mutableVec)
+ segment.computeLerpPoint(1.0f, mutableVec)
assertThat(mutableVec).isEqualTo(MutableVec(8f, -5f))
}
@@ -452,13 +468,13 @@
val segment = ImmutableSegment(ImmutableVec(6f, 3f), ImmutableVec(8f, -5f))
val mutableVec = MutableVec(0f, 0f)
- segment.populateLerpPoint(0.2f, mutableVec)
+ segment.computeLerpPoint(0.2f, mutableVec)
assertThat(mutableVec.isAlmostEqual(MutableVec(6.4f, 1.4f), .000001f)).isTrue()
- segment.populateLerpPoint(0.5f, mutableVec)
+ segment.computeLerpPoint(0.5f, mutableVec)
assertThat(mutableVec.isAlmostEqual(MutableVec(7f, -1f), .000001f)).isTrue()
- segment.populateLerpPoint(0.9f, mutableVec)
+ segment.computeLerpPoint(0.9f, mutableVec)
assertThat(mutableVec.isAlmostEqual(MutableVec(7.8f, -4.2f), .000001f)).isTrue()
}
@@ -467,10 +483,10 @@
val segment = ImmutableSegment(ImmutableVec(6f, 3f), ImmutableVec(8f, -5f))
val mutableVec = MutableVec(0f, 0f)
- segment.populateLerpPoint(-1f, mutableVec)
+ segment.computeLerpPoint(-1f, mutableVec)
assertThat(mutableVec.isAlmostEqual(MutableVec(4f, 11f), .000001f)).isTrue()
- segment.populateLerpPoint(1.3f, mutableVec)
+ segment.computeLerpPoint(1.3f, mutableVec)
assertThat(mutableVec.isAlmostEqual(MutableVec(8.6f, -7.4f), .000001f)).isTrue()
}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/TriangleTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/TriangleTest.kt
index 0309d82..2091d2c 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/TriangleTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/TriangleTest.kt
@@ -24,20 +24,20 @@
@RunWith(JUnit4::class)
class TriangleTest {
+ @Test
fun signedArea_correctlyReturnsArea() {
val triangle0 =
ImmutableTriangle(ImmutableVec(-1f, -3f), ImmutableVec(3f, -3f), ImmutableVec(-3f, -1f))
val triangle1 =
- MutableTriangle(ImmutableVec(1f, 1f), ImmutableVec(-5f, 4f), ImmutableVec(-1f, -2f))
+ MutableTriangle(MutableVec(1f, 1f), MutableVec(-5f, 4f), MutableVec(-1f, -2f))
val triangle2 =
ImmutableTriangle(ImmutableVec(-5f, 5f), ImmutableVec(2f, 4f), ImmutableVec(1f, -5f))
- val triangle3 =
- MutableTriangle(ImmutableVec(1f, -4f), ImmutableVec(3f, 1f), ImmutableVec(4f, 2f))
+ val triangle3 = MutableTriangle(MutableVec(1f, -4f), MutableVec(3f, 1f), MutableVec(4f, 2f))
- assertThat(triangle0.signedArea).isWithin(1e-5f).of(4f)
- assertThat(triangle1.signedArea).isWithin(1e-5f).of(12f)
- assertThat(triangle2.signedArea).isWithin(1e-5f).of(-32f)
- assertThat(triangle3.signedArea).isWithin(1e-5f).of(-1.5f)
+ assertThat(triangle0.computeSignedArea()).isWithin(1e-5f).of(4f)
+ assertThat(triangle1.computeSignedArea()).isWithin(1e-5f).of(12f)
+ assertThat(triangle2.computeSignedArea()).isWithin(1e-5f).of(-32f)
+ assertThat(triangle3.computeSignedArea()).isWithin(1e-5f).of(-1.5f)
}
@Test
@@ -45,104 +45,98 @@
val triangle0 =
ImmutableTriangle(ImmutableVec(3f, 2f), ImmutableVec(5f, 2f), ImmutableVec(2f, 2f))
val triangle1 =
- MutableTriangle(ImmutableVec(-1f, 2f), ImmutableVec(0f, 0f), ImmutableVec(1f, -2f))
+ MutableTriangle(MutableVec(-1f, 2f), MutableVec(0f, 0f), MutableVec(1f, -2f))
val triangle2 =
ImmutableTriangle(ImmutableVec(0f, 1f), ImmutableVec(-2f, 3f), ImmutableVec(-2f, 3f))
- val triangle3 =
- MutableTriangle(ImmutableVec(5f, 2f), ImmutableVec(5f, 2f), ImmutableVec(5f, 2f))
+ val triangle3 = MutableTriangle(MutableVec(5f, 2f), MutableVec(5f, 2f), MutableVec(5f, 2f))
- assertThat(triangle0.signedArea).isWithin(1e-5f).of(0f)
- assertThat(triangle1.signedArea).isWithin(1e-5f).of(0f)
- assertThat(triangle2.signedArea).isWithin(1e-5f).of(0f)
- assertThat(triangle3.signedArea).isWithin(1e-5f).of(0f)
+ assertThat(triangle0.computeSignedArea()).isWithin(1e-5f).of(0f)
+ assertThat(triangle1.computeSignedArea()).isWithin(1e-5f).of(0f)
+ assertThat(triangle2.computeSignedArea()).isWithin(1e-5f).of(0f)
+ assertThat(triangle3.computeSignedArea()).isWithin(1e-5f).of(0f)
}
@Test
fun boundingBox_correctlyReturnsBoundingBox() {
- val triangle0 =
- MutableTriangle(ImmutableVec(1f, 1f), ImmutableVec(5f, 2f), ImmutableVec(2f, 2f))
+ val triangle0 = MutableTriangle(MutableVec(1f, 1f), MutableVec(5f, 2f), MutableVec(2f, 2f))
val triangle1 =
ImmutableTriangle(ImmutableVec(-1f, -2f), ImmutableVec(0f, 0f), ImmutableVec(1f, -2f))
val triangle2 =
- MutableTriangle(ImmutableVec(0f, 1f), ImmutableVec(-2f, 3f), ImmutableVec(-2f, 3f))
+ MutableTriangle(MutableVec(0f, 1f), MutableVec(-2f, 3f), MutableVec(-2f, 3f))
val triangle3 =
ImmutableTriangle(ImmutableVec(5f, 2f), ImmutableVec(5f, 2f), ImmutableVec(5f, 2f))
- assertThat(triangle0.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(1f, 1f), ImmutablePoint(5f, 2f)))
- assertThat(triangle1.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(-1f, -2f), ImmutablePoint(1f, 0f)))
- assertThat(triangle2.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(-2f, 1f), ImmutablePoint(0f, 3f)))
- assertThat(triangle3.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(5f, 2f), ImmutablePoint(5f, 2f)))
+ assertThat(triangle0.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(1f, 1f), ImmutableVec(5f, 2f)))
+ assertThat(triangle1.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(-1f, -2f), ImmutableVec(1f, 0f)))
+ assertThat(triangle2.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(-2f, 1f), ImmutableVec(0f, 3f)))
+ assertThat(triangle3.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(5f, 2f), ImmutableVec(5f, 2f)))
}
@Test
fun boundingBox_forDegenerateTriangle_correctlyReturnsBoundingBox() {
- val triangle0 =
- MutableTriangle(ImmutableVec(3f, 2f), ImmutableVec(5f, 2f), ImmutableVec(2f, 2f))
+ val triangle0 = MutableTriangle(MutableVec(3f, 2f), MutableVec(5f, 2f), MutableVec(2f, 2f))
val triangle1 =
- MutableTriangle(ImmutableVec(-1f, 2f), ImmutableVec(0f, 0f), ImmutableVec(1f, -2f))
+ MutableTriangle(MutableVec(-1f, 2f), MutableVec(0f, 0f), MutableVec(1f, -2f))
val triangle2 =
ImmutableTriangle(ImmutableVec(0f, 1f), ImmutableVec(-2f, 3f), ImmutableVec(-2f, 3f))
val triangle3 =
ImmutableTriangle(ImmutableVec(5f, 2f), ImmutableVec(5f, 2f), ImmutableVec(5f, 2f))
- assertThat(triangle0.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(2f, 2f), ImmutablePoint(5f, 2f)))
- assertThat(triangle1.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(-1f, -2f), ImmutablePoint(1f, 2f)))
- assertThat(triangle2.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(-2f, 1f), ImmutablePoint(0f, 3f)))
- assertThat(triangle3.boundingBox)
- .isEqualTo(ImmutableBox.fromTwoPoints(ImmutablePoint(5f, 2f), ImmutablePoint(5f, 2f)))
+ assertThat(triangle0.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(2f, 2f), ImmutableVec(5f, 2f)))
+ assertThat(triangle1.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(-1f, -2f), ImmutableVec(1f, 2f)))
+ assertThat(triangle2.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(-2f, 1f), ImmutableVec(0f, 3f)))
+ assertThat(triangle3.computeBoundingBox())
+ .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(5f, 2f), ImmutableVec(5f, 2f)))
}
@Test
fun populateBoundingBox_correctlyReturnsBoundingBox() {
- val triangle0 =
- MutableTriangle(ImmutableVec(1f, 1f), ImmutableVec(5f, 2f), ImmutableVec(2f, 2f))
+ val triangle0 = MutableTriangle(MutableVec(1f, 1f), MutableVec(5f, 2f), MutableVec(2f, 2f))
val triangle1 =
ImmutableTriangle(ImmutableVec(-1f, -2f), ImmutableVec(0f, 0f), ImmutableVec(1f, -2f))
val triangle2 =
ImmutableTriangle(ImmutableVec(0f, 1f), ImmutableVec(-2f, 3f), ImmutableVec(-2f, 3f))
- val triangle3 =
- MutableTriangle(ImmutableVec(5f, 2f), ImmutableVec(5f, 2f), ImmutableVec(5f, 2f))
+ val triangle3 = MutableTriangle(MutableVec(5f, 2f), MutableVec(5f, 2f), MutableVec(5f, 2f))
val box0 = MutableBox()
val box1 = MutableBox()
val box2 = MutableBox()
val box3 = MutableBox()
- triangle0.populateBoundingBox(box0)
- triangle1.populateBoundingBox(box1)
- triangle2.populateBoundingBox(box2)
- triangle3.populateBoundingBox(box3)
+ triangle0.computeBoundingBox(box0)
+ triangle1.computeBoundingBox(box1)
+ triangle2.computeBoundingBox(box2)
+ triangle3.computeBoundingBox(box3)
assertThat(box0)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(1f, 1f), ImmutablePoint(5f, 2f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(1f, 1f), ImmutableVec(5f, 2f))
)
assertThat(box1)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(-1f, -2f), ImmutablePoint(1f, 0f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(-1f, -2f), ImmutableVec(1f, 0f))
)
assertThat(box2)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(-2f, 1f), ImmutablePoint(0f, 3f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(-2f, 1f), ImmutableVec(0f, 3f))
)
assertThat(box3)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(5f, 2f), ImmutablePoint(5f, 2f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(5f, 2f), ImmutableVec(5f, 2f))
)
}
@Test
fun populateBoundingBox_forDegenerateTriangle_correctlyReturnsBoundingBox() {
- val triangle0 =
- MutableTriangle(ImmutableVec(3f, 2f), ImmutableVec(5f, 2f), ImmutableVec(2f, 2f))
+ val triangle0 = MutableTriangle(MutableVec(3f, 2f), MutableVec(5f, 2f), MutableVec(2f, 2f))
val triangle1 =
- MutableTriangle(ImmutableVec(-1f, 2f), ImmutableVec(0f, 0f), ImmutableVec(1f, -2f))
+ MutableTriangle(MutableVec(-1f, 2f), MutableVec(0f, 0f), MutableVec(1f, -2f))
val triangle2 =
ImmutableTriangle(ImmutableVec(0f, 1f), ImmutableVec(-2f, 3f), ImmutableVec(-2f, 3f))
val triangle3 =
@@ -152,26 +146,26 @@
val box2 = MutableBox()
val box3 = MutableBox()
- triangle0.populateBoundingBox(box0)
- triangle1.populateBoundingBox(box1)
- triangle2.populateBoundingBox(box2)
- triangle3.populateBoundingBox(box3)
+ triangle0.computeBoundingBox(box0)
+ triangle1.computeBoundingBox(box1)
+ triangle2.computeBoundingBox(box2)
+ triangle3.computeBoundingBox(box3)
assertThat(box0)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(2f, 2f), ImmutablePoint(5f, 2f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(2f, 2f), ImmutableVec(5f, 2f))
)
assertThat(box1)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(-1f, -2f), ImmutablePoint(1f, 2f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(-1f, -2f), ImmutableVec(1f, 2f))
)
assertThat(box2)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(-2f, 1f), ImmutablePoint(0f, 3f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(-2f, 1f), ImmutableVec(0f, 3f))
)
assertThat(box3)
.isEqualTo(
- MutableBox().fillFromTwoPoints(ImmutablePoint(5f, 2f), ImmutablePoint(5f, 2f))
+ MutableBox().populateFromTwoPoints(ImmutableVec(5f, 2f), ImmutableVec(5f, 2f))
)
}
}
diff --git a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/VecTest.kt b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/VecTest.kt
index 5bcc734..377a931 100644
--- a/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/VecTest.kt
+++ b/ink/ink-geometry/src/jvmAndroidTest/kotlin/androidx/ink/geometry/VecTest.kt
@@ -61,37 +61,40 @@
@Test
fun direction_returnsCorrectValue() {
- assertThat(ImmutableVec(5f, 0f).direction).isEqualTo(Angle.degreesToRadians(0f))
- assertThat(ImmutableVec(0f, 5f).direction).isEqualTo(Angle.degreesToRadians(90f))
- assertThat(ImmutableVec(-5f, 0f).direction).isEqualTo(Angle.degreesToRadians(180f))
- assertThat(ImmutableVec(0f, -5f).direction).isEqualTo(Angle.degreesToRadians(-90f))
- assertThat(ImmutableVec(5f, 5f).direction).isEqualTo(Angle.degreesToRadians(45f))
- assertThat(ImmutableVec(-5f, 5f).direction).isEqualTo(Angle.degreesToRadians(135f))
- assertThat(ImmutableVec(-5f, -5f).direction).isEqualTo(Angle.degreesToRadians(-135f))
- assertThat(ImmutableVec(5f, -5f).direction).isEqualTo(Angle.degreesToRadians(-45f))
+ assertThat(ImmutableVec(5f, 0f).computeDirection()).isEqualTo(Angle.degreesToRadians(0f))
+ assertThat(ImmutableVec(0f, 5f).computeDirection()).isEqualTo(Angle.degreesToRadians(90f))
+ assertThat(ImmutableVec(-5f, 0f).computeDirection()).isEqualTo(Angle.degreesToRadians(180f))
+ assertThat(ImmutableVec(0f, -5f).computeDirection()).isEqualTo(Angle.degreesToRadians(-90f))
+ assertThat(ImmutableVec(5f, 5f).computeDirection()).isEqualTo(Angle.degreesToRadians(45f))
+ assertThat(ImmutableVec(-5f, 5f).computeDirection()).isEqualTo(Angle.degreesToRadians(135f))
+ assertThat(ImmutableVec(-5f, -5f).computeDirection())
+ .isEqualTo(Angle.degreesToRadians(-135f))
+ assertThat(ImmutableVec(5f, -5f).computeDirection()).isEqualTo(Angle.degreesToRadians(-45f))
}
@Test
fun direction_whenVecContainsZero_returnsCorrectValue() {
- assertThat(ImmutableVec(+0f, +0f).direction).isEqualTo(Angle.degreesToRadians(0f))
- assertThat(ImmutableVec(+0f, -0f).direction).isEqualTo(Angle.degreesToRadians(-0f))
- assertThat(ImmutableVec(-0f, +0f).direction).isEqualTo(Angle.degreesToRadians(180f))
- assertThat(ImmutableVec(-0f, -0f).direction).isEqualTo(Angle.degreesToRadians(-180f))
+ assertThat(ImmutableVec(+0f, +0f).computeDirection()).isEqualTo(Angle.degreesToRadians(0f))
+ assertThat(ImmutableVec(+0f, -0f).computeDirection()).isEqualTo(Angle.degreesToRadians(-0f))
+ assertThat(ImmutableVec(-0f, +0f).computeDirection())
+ .isEqualTo(Angle.degreesToRadians(180f))
+ assertThat(ImmutableVec(-0f, -0f).computeDirection())
+ .isEqualTo(Angle.degreesToRadians(-180f))
}
@Test
fun unitVec_returnsCorrectValue() {
- assertThat(ImmutableVec(4f, 0f).unitVec).isEqualTo(ImmutableVec(1f, 0f))
- assertThat(MutableVec(0f, -25f).unitVec).isEqualTo(ImmutableVec(0f, -1f))
+ assertThat(ImmutableVec(4f, 0f).computeUnitVec()).isEqualTo(ImmutableVec(1f, 0f))
+ assertThat(MutableVec(0f, -25f).computeUnitVec()).isEqualTo(ImmutableVec(0f, -1f))
assertThat(
ImmutableVec(30f, 30f)
- .unitVec
+ .computeUnitVec()
.isAlmostEqual(ImmutableVec(sqrt(.5f), sqrt(.5f)), tolerance = 0.000001f)
)
.isTrue()
assertThat(
MutableVec(-.05f, -.05f)
- .unitVec
+ .computeUnitVec()
.isAlmostEqual(ImmutableVec(-sqrt(.5f), -sqrt(.5f)), tolerance = 0.000001f)
)
.isTrue()
@@ -99,33 +102,33 @@
@Test
fun unitVec_whenVecContainsZeroes_returnsCorrectValue() {
- assertThat(ImmutableVec(+0f, 0f).unitVec).isEqualTo(ImmutableVec(1f, 0f))
- assertThat(MutableVec(-0f, 0f).unitVec).isEqualTo(ImmutableVec(-1f, 0f))
+ assertThat(ImmutableVec(+0f, 0f).computeUnitVec()).isEqualTo(ImmutableVec(1f, 0f))
+ assertThat(MutableVec(-0f, 0f).computeUnitVec()).isEqualTo(ImmutableVec(-1f, 0f))
}
@Test
fun populateUnitVec_populatesCorrectValue() {
val mutableVec = MutableVec(0f, 0f)
- MutableVec(4f, 0f).populateUnitVec(mutableVec)
+ MutableVec(4f, 0f).computeUnitVec(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(1f, 0f))
- ImmutableVec(0f, -25f).populateUnitVec(mutableVec)
+ ImmutableVec(0f, -25f).computeUnitVec(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(0f, -1f))
- MutableVec(30f, 30f).populateUnitVec(mutableVec)
+ MutableVec(30f, 30f).computeUnitVec(mutableVec)
assertThat(mutableVec.isAlmostEqual(ImmutableVec(sqrt(.5f), sqrt(.5f)))).isTrue()
- ImmutableVec(-.05f, -.05f).populateUnitVec(mutableVec)
+ ImmutableVec(-.05f, -.05f).computeUnitVec(mutableVec)
assertThat(mutableVec.isAlmostEqual(ImmutableVec(-sqrt(.5f), -sqrt(.5f)))).isTrue()
}
@Test
fun populateUnitVec_whenVecContainsZeroes_populatesCorrectValue() {
val mutableVec = MutableVec(0f, 0f)
- MutableVec(+0f, 0f).populateUnitVec(mutableVec)
+ MutableVec(+0f, 0f).computeUnitVec(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(1f, 0f))
- ImmutableVec(-0f, -0f).populateUnitVec(mutableVec)
+ ImmutableVec(-0f, -0f).computeUnitVec(mutableVec)
assertThat(mutableVec).isEqualTo(ImmutableVec(-1f, 0f))
}
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/BatchedMotionEvent.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/BatchedMotionEvent.java
index 9595006..9639efb 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/BatchedMotionEvent.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/BatchedMotionEvent.java
@@ -71,7 +71,7 @@
return mMotionEvent;
}
- public @NonNull int getPointerCount() {
+ public int getPointerCount() {
return mPointerCount;
}
diff --git a/libraryversions.toml b/libraryversions.toml
index efc3ca3..3980454 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -1,9 +1,9 @@
[versions]
-ACTIVITY = "1.10.0-beta01"
+ACTIVITY = "1.10.0-alpha02"
ANNOTATION = "1.9.0-alpha02"
ANNOTATION_EXPERIMENTAL = "1.5.0-alpha01"
APPCOMPAT = "1.8.0-alpha01"
-APPSEARCH = "1.1.0-alpha04"
+APPSEARCH = "1.1.0-alpha05"
ARCH_CORE = "2.3.0-alpha01"
ASYNCLAYOUTINFLATER = "1.1.0-alpha02"
AUTOFILL = "1.3.0-alpha02"
@@ -21,12 +21,12 @@
COLLECTION = "1.5.0-alpha01"
COMPOSE = "1.8.0-alpha01"
COMPOSE_MATERIAL3 = "1.4.0-alpha01"
-COMPOSE_MATERIAL3_ADAPTIVE = "1.1.0-alpha01"
+COMPOSE_MATERIAL3_ADAPTIVE = "1.1.0-alpha02"
COMPOSE_MATERIAL3_COMMON = "1.0.0-alpha01"
COMPOSE_RUNTIME_TRACING = "1.0.0-beta01"
-CONSTRAINTLAYOUT = "2.2.0-alpha14"
-CONSTRAINTLAYOUT_COMPOSE = "1.1.0-alpha14"
-CONSTRAINTLAYOUT_CORE = "1.1.0-alpha14"
+CONSTRAINTLAYOUT = "2.2.0-beta01"
+CONSTRAINTLAYOUT_COMPOSE = "1.1.0-beta01"
+CONSTRAINTLAYOUT_CORE = "1.1.0-beta01"
CONTENTPAGER = "1.1.0-alpha01"
COORDINATORLAYOUT = "1.3.0-alpha02"
CORE = "1.15.0-alpha02"
@@ -40,10 +40,10 @@
CORE_PERFORMANCE = "1.0.0"
CORE_REMOTEVIEWS = "1.1.0-rc01"
CORE_ROLE = "1.2.0-alpha01"
-CORE_SPLASHSCREEN = "1.2.0-alpha01"
+CORE_SPLASHSCREEN = "1.2.0-alpha02"
CORE_TELECOM = "1.0.0-alpha4"
CORE_UWB = "1.0.0-alpha08"
-CREDENTIALS = "1.5.0-alpha04"
+CREDENTIALS = "1.5.0-alpha05"
CREDENTIALS_E2EE_QUARANTINE = "1.0.0-alpha02"
CREDENTIALS_FIDO_QUARANTINE = "1.0.0-alpha02"
CURSORADAPTER = "1.1.0-alpha01"
@@ -96,7 +96,7 @@
MEDIA = "1.7.0-rc01"
MEDIAROUTER = "1.8.0-alpha01"
METRICS = "1.0.0-beta02"
-NAVIGATION = "2.8.0-rc01"
+NAVIGATION = "2.9.0-alpha01"
PAGING = "3.4.0-alpha01"
PALETTE = "1.1.0-alpha01"
PDF = "1.0.0-alpha02"
@@ -109,9 +109,9 @@
PRIVACYSANDBOX_SDKRUNTIME = "1.0.0-alpha14"
PRIVACYSANDBOX_TOOLS = "1.0.0-alpha09"
PRIVACYSANDBOX_UI = "1.0.0-alpha09"
-PROFILEINSTALLER = "1.4.0-beta01"
+PROFILEINSTALLER = "1.4.0-rc01"
RECOMMENDATION = "1.1.0-alpha01"
-RECYCLERVIEW = "1.4.0-beta01"
+RECYCLERVIEW = "1.4.0-rc01"
RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
REMOTECALLBACK = "1.0.0-alpha02"
RESOURCEINSPECTION = "1.1.0-alpha01"
@@ -134,7 +134,7 @@
SQLITE = "2.5.0-alpha07"
SQLITE_INSPECTOR = "2.1.0-alpha01"
STABLE_AIDL = "1.0.0-alpha01"
-STARTUP = "1.2.0-beta01"
+STARTUP = "1.2.0-rc01"
SWIPEREFRESHLAYOUT = "1.2.0-alpha01"
TESTEXT = "1.0.0-alpha03"
TESTSCREENSHOT = "1.0.0-alpha01"
@@ -168,7 +168,7 @@
WEAR_WATCHFACE = "1.3.0-alpha03"
WEBKIT = "1.12.0-beta01"
# Adding a comment to prevent merge conflicts for Window artifact
-WINDOW = "1.4.0-alpha01"
+WINDOW = "1.4.0-alpha02"
WINDOW_EXTENSIONS = "1.4.0-beta01"
WINDOW_EXTENSIONS_CORE = "1.1.0-alpha01"
WINDOW_SIDECAR = "1.0.0-rc01"
diff --git a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
index b4b5ecc..4799f00 100644
--- a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
@@ -83,8 +83,10 @@
RestrictToDetector.RESTRICTED,
ObsoleteCompatDetector.ISSUE,
ReplaceWithDetector.ISSUE,
- // This issue is only enabled when `-Pandroidx.migrateArrayAnnotations=true`.
- ArrayNullnessMigration.ISSUE,
+ // This issue is only enabled when `-Pandroidx.useJSpecifyAnnotations=true`.
+ JSpecifyNullnessMigration.ISSUE,
+ TypeMirrorToString.ISSUE,
+ BanNullMarked.ISSUE,
)
}
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/ArrayNullnessMigration.kt b/lint-checks/src/main/java/androidx/build/lint/ArrayNullnessMigration.kt
deleted file mode 100644
index 7e741be..0000000
--- a/lint-checks/src/main/java/androidx/build/lint/ArrayNullnessMigration.kt
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.build.lint
-
-import com.android.tools.lint.client.api.UElementHandler
-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.Location
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.isKotlin
-import com.intellij.psi.PsiArrayType
-import com.intellij.psi.PsiEllipsisType
-import java.util.EnumSet
-import org.jetbrains.uast.UAnnotation
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UField
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.UParameter
-
-/**
- * Repositions nullness annotations on arrays to facilitate migrating the nullness annotations to
- * TYPE_USE. See the issue description in the companion object for more detail.
- */
-class ArrayNullnessMigration : Detector(), Detector.UastScanner {
- override fun getApplicableUastTypes() = listOf(UAnnotation::class.java)
-
- override fun createUastHandler(context: JavaContext): UElementHandler {
- return AnnotationChecker(context)
- }
-
- private inner class AnnotationChecker(val context: JavaContext) : UElementHandler() {
- override fun visitAnnotation(node: UAnnotation) {
- // Nullness annotations are only relevant for Java source.
- if (isKotlin(node.lang)) return
-
- // Verify this is a nullness annotation.
- val annotationName = node.qualifiedName ?: return
- if (annotationName !in nullnessAnnotations) return
-
- // Find the type of the annotated element, and only continue if it is an array.
- val annotated = node.uastParent ?: return
- val type =
- when (annotated) {
- is UParameter -> annotated.type
- is UMethod -> annotated.returnType
- is UField -> annotated.type
- else -> return
- }
- if (type !is PsiArrayType) return
-
- // Determine the file location for the autofix. This is a bit complicated because it
- // needs to avoid editing the wrong thing, like the doc comment preceding a method, but
- // also is doing some reformatting in the area around the annotation.
- // This is where the annotation itself is located.
- val annotationLocation = context.getLocation(node)
- // This is where the element being annotated is located.
- val annotatedLocation = context.getLocation(annotated as UElement)
- // If the annotation and annotated element aren't on the same line, that probably means
- // the annotation is on its own line, with indentation before it. To also get rid of
- // that indentation, start the range at the start of the annotation's line.
- // If the annotation and annotated element are on the same line, just start at the
- // annotation starting place to avoid including e.g. other parameters.
- val startLocation =
- if (annotatedLocation.start!!.sameLine(annotationLocation.start!!)) {
- annotationLocation.start!!
- } else {
- Location.create(
- context.file,
- context.getContents()!!.toString(),
- annotationLocation.start!!.line
- )
- .start!!
- }
- val fixLocation =
- Location.create(annotatedLocation.file, startLocation, annotatedLocation.end)
-
- // Part 1 of the fix: remove the original annotation
- val annotationString = node.asSourceString()
- val removeOriginalAnnotation =
- fix()
- .replace()
- .range(fixLocation)
- // In addition to the annotation, also remove any extra whitespace and trailing
- // new line. The reformat option unfortunately doesn't do this.
- .pattern("(( )*$annotationString ?\n?)")
- .with("")
- // Only remove one instance of the annotation.
- .repeatedly(false)
- .autoFix()
- .build()
-
- // Vararg types are also arrays, determine which array marker is present here.
- val arraySuffix =
- if (type is PsiEllipsisType) {
- "..."
- } else {
- "[]"
- }
- // Part 2 of the fix: add a new annotation.
- val addNewAnnotation =
- fix()
- .replace()
- .range(fixLocation)
- .text(arraySuffix)
- .with(" $annotationString $arraySuffix")
- // Only add one instance of the annotation. This will replace the first instance
- // of []/..., which is correct. In `String @Nullable [][]` the annotation
- // applies to the outer `String[][]` type, while in `String[] @Nullable []` it
- // applies to the inner `String[]` arrays.
- .repeatedly(false)
- .autoFix()
- .build()
-
- // Combine the two elements of the fix and report.
- val fix =
- fix()
- .name("Move annotation")
- .composite()
- .add(removeOriginalAnnotation)
- .add(addNewAnnotation)
- .autoFix()
- .build()
-
- val incident =
- Incident(context)
- .message("Nullness annotation on array will apply to element")
- .issue(ISSUE)
- .location(context.getLocation(annotated as UElement))
- .scope(annotated)
- .fix(fix)
- context.report(incident)
- }
- }
-
- companion object {
- val nullnessAnnotations =
- listOf(
- "androidx.annotation.NonNull",
- "androidx.annotation.Nullable",
- )
- val ISSUE =
- Issue.create(
- "ArrayMigration",
- "Migrate arrays to type-use nullness annotations",
- """
- When nullness annotations do not target TYPE_USE, the following definition means
- that the type of `arg` is nullable:
- @Nullable String[] arg
- However, if the annotation targets TYPE_USE, it now applies to the component
- type of the array, meaning that `arg`'s type is an array of nullable strings.
- To retain the original meaning, the definition needs to be changed to this:
- String @Nullable [] arg
- This check performs that migration to enable converting nullness annotations to
- target TYPE_USE.
- """,
- Category.CORRECTNESS,
- 5,
- Severity.ERROR,
- Implementation(
- ArrayNullnessMigration::class.java,
- EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
- )
- )
- }
-}
diff --git a/lint-checks/src/main/java/androidx/build/lint/BanNullMarked.kt b/lint-checks/src/main/java/androidx/build/lint/BanNullMarked.kt
new file mode 100644
index 0000000..9b735cc
--- /dev/null
+++ b/lint-checks/src/main/java/androidx/build/lint/BanNullMarked.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+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
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UAnnotation
+
+class BanNullMarked : Detector(), Detector.UastScanner {
+
+ override fun getApplicableUastTypes() = listOf(UAnnotation::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ return AnnotationChecker(context)
+ }
+
+ private inner class AnnotationChecker(val context: JavaContext) : UElementHandler() {
+ override fun visitAnnotation(node: UAnnotation) {
+ if (node.qualifiedName != "org.jspecify.annotations.NullMarked") return
+
+ val incident =
+ Incident(context)
+ .issue(ISSUE)
+ .location(context.getLocation(node))
+ .scope(node)
+ .message("Should not use @NullMarked annotation")
+ context.report(incident)
+ }
+ }
+
+ companion object {
+ val ISSUE =
+ Issue.create(
+ "BanNullMarked",
+ "Should not use @NullMarked annotation",
+ "Usage of the @NullMarked annotation is not allowed because lint and metalava do not support it",
+ Category.CORRECTNESS,
+ 5,
+ Severity.ERROR,
+ Implementation(BanNullMarked::class.java, Scope.JAVA_FILE_SCOPE)
+ )
+ }
+}
diff --git a/lint-checks/src/main/java/androidx/build/lint/JSpecifyNullnessMigration.kt b/lint-checks/src/main/java/androidx/build/lint/JSpecifyNullnessMigration.kt
new file mode 100644
index 0000000..a57fe59
--- /dev/null
+++ b/lint-checks/src/main/java/androidx/build/lint/JSpecifyNullnessMigration.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+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.LintFix
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.isKotlin
+import com.intellij.psi.PsiArrayType
+import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiEllipsisType
+import com.intellij.psi.PsiPrimitiveType
+import java.util.EnumSet
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UField
+import org.jetbrains.uast.ULocalVariable
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UParameter
+
+/**
+ * Repositions nullness annotations to facilitate migrating the nullness annotations to JSpecify
+ * TYPE_USE annotations. See the issue description in the companion object for more detail.
+ */
+class JSpecifyNullnessMigration : Detector(), Detector.UastScanner {
+ override fun getApplicableUastTypes() = listOf(UAnnotation::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ return AnnotationChecker(context)
+ }
+
+ private inner class AnnotationChecker(val context: JavaContext) : UElementHandler() {
+ override fun visitAnnotation(node: UAnnotation) {
+ // Nullness annotations are only relevant for Java source.
+ if (isKotlin(node.lang)) return
+
+ // Verify this is a nullness annotation.
+ val annotationName = node.qualifiedName ?: return
+ if (annotationName !in nullnessAnnotations.keys) return
+ val replacementAnnotationName = nullnessAnnotations[annotationName]!!
+
+ val fix = createFix(node, replacementAnnotationName)
+ val incident =
+ Incident(context)
+ .message("Switch nullness annotation to JSpecify")
+ .issue(ISSUE)
+ .location(context.getLocation(node as UElement))
+ .scope(node)
+ .fix(fix)
+ context.report(incident)
+ }
+
+ fun createFix(node: UAnnotation, replacementAnnotationName: String): LintFix? {
+ // Find the type of the annotated element.
+ val annotated = node.uastParent ?: return null
+ val type =
+ when (annotated) {
+ is UParameter -> annotated.type
+ is UMethod -> annotated.returnType
+ is UField -> annotated.type
+ is ULocalVariable -> annotated.type
+ else -> return null
+ } ?: return null
+
+ // Determine the file location for the autofix. This is a bit complicated because it
+ // needs to avoid editing the wrong thing, like the doc comment preceding a method, but
+ // also is doing some reformatting in the area around the annotation.
+ // This is where the annotation itself is located.
+ val annotationLocation = context.getLocation(node)
+ // This is where the element being annotated is located.
+ val annotatedLocation = context.getLocation(annotated as UElement)
+ // If the annotation and annotated element aren't on the same line, that probably means
+ // the annotation is on its own line, with indentation before it. To also get rid of
+ // that indentation, start the range at the start of the annotation's line.
+ // If the annotation and annotated element are on the same line, just start at the
+ // annotation starting place to avoid including e.g. other parameters.
+ val annotatedStart = annotatedLocation.start ?: return null
+ val annotationStart = annotationLocation.start ?: return null
+ val startLocation =
+ if (annotatedStart.sameLine(annotationStart)) {
+ annotationStart
+ } else {
+ Location.create(
+ context.file,
+ context.getContents()!!.toString(),
+ annotationStart.line
+ )
+ .start!!
+ }
+ val fixLocation =
+ Location.create(annotatedLocation.file, startLocation, annotatedLocation.end)
+
+ // Part 1 of the fix: remove the original annotation
+ val annotationString = node.asSourceString()
+ val removeOriginalAnnotation =
+ fix()
+ .replace()
+ .range(fixLocation)
+ // In addition to the annotation, also remove any extra whitespace and trailing
+ // new line. The reformat option unfortunately doesn't do this.
+ .pattern("(( )*$annotationString ?\n?)")
+ .with("")
+ // Only remove one instance of the annotation.
+ .repeatedly(false)
+ .autoFix()
+ .build()
+
+ // The jspecify annotations can't be applied to primitive types (since primitives are
+ // non-null by definition) or local variables, so just remove the annotation in those
+ // cases. For all other cases, also add a new annotation to the correct position.
+ return if (type is PsiPrimitiveType || annotated is ULocalVariable) {
+ removeOriginalAnnotation
+ } else {
+ // Create a regex pattern for where to insert the annotation. The replacement lint
+ // removes the first capture group (section in parentheses) of the supplied regex.
+ // Since this fix is really just to insert an annotation, use an empty capture group
+ // so nothing is removed.
+ val (prefix, textToReplace) =
+ when {
+ // For a vararg type where the component type is an array, the annotation
+ // goes before the array instead of the vararg ("String @NonNull []..."),
+ // so only match the "..." when the component isn't an array.
+ type is PsiEllipsisType && type.componentType !is PsiArrayType ->
+ Pair(" ", "()\\.\\.\\.")
+ type is PsiArrayType -> Pair(" ", "()\\[\\]")
+ // Make sure to match the right usage of the class name: find the name
+ // preceded by a space or dot, and followed by a space, open angle bracket,
+ // or newline character.
+ type is PsiClassType -> Pair("", "[ .]()${type.className}[ <\\n\\r]")
+ else -> Pair("", "()${type.presentableText}")
+ }
+ val replacement = "$prefix@$replacementAnnotationName "
+
+ // Part 2 of the fix: add a new annotation.
+ val addNewAnnotation =
+ fix()
+ .replace()
+ .range(fixLocation)
+ .pattern(textToReplace)
+ .with(replacement)
+ // Only add one instance of the annotation. For nested array types, this
+ // will replace the first instance of []/..., which is correct. In
+ // `String @Nullable [][]` the annotation applies to the outer `String[][]`
+ // type, while in `String[] @Nullable []` it applies to the inner `String[]`
+ // arrays.
+ .repeatedly(false)
+ .shortenNames()
+ .autoFix()
+ .build()
+
+ // Combine the two elements of the fix.
+ return fix()
+ .name("Move annotation")
+ .composite()
+ .add(removeOriginalAnnotation)
+ .add(addNewAnnotation)
+ .autoFix()
+ .build()
+ }
+ }
+ }
+
+ companion object {
+ val nullnessAnnotations =
+ mapOf(
+ "androidx.annotation.NonNull" to "NonNull",
+ "androidx.annotation.Nullable" to "Nullable",
+ )
+ val ISSUE =
+ Issue.create(
+ "JSpecifyNullness",
+ "Migrate nullness annotations to type-use position",
+ """
+ Switches from AndroidX nullness annotations to JSpecify, which are type-use.
+ Type-use annotations have different syntactic positions than non-type-use
+ annotations in some cases.
+
+ For instance, when nullness annotations do not target TYPE_USE, the following
+ definition means that the type of `arg` is nullable:
+ @Nullable String[] arg
+ However, if the annotation targets TYPE_USE, it now applies to the component
+ type of the array, meaning that `arg`'s type is an array of nullable strings.
+ To retain the original meaning, the definition needs to be changed to this:
+ String @Nullable [] arg
+
+ Type-use nullness annotations must go before the simple class name of a
+ qualified type. For instance, `java.lang.@Nullable String` is required instead
+ of `@Nullable java.lang.String`.
+ """,
+ Category.CORRECTNESS,
+ 5,
+ Severity.ERROR,
+ Implementation(
+ JSpecifyNullnessMigration::class.java,
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+ )
+ )
+ }
+}
diff --git a/lint-checks/src/main/java/androidx/build/lint/TypeMirrorToString.kt b/lint-checks/src/main/java/androidx/build/lint/TypeMirrorToString.kt
new file mode 100644
index 0000000..408f826
--- /dev/null
+++ b/lint-checks/src/main/java/androidx/build/lint/TypeMirrorToString.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+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
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiClass
+import org.jetbrains.uast.UCallExpression
+
+class TypeMirrorToString : Detector(), SourceCodeScanner {
+
+ override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ return TypeMirrorHandler(context)
+ }
+
+ private inner class TypeMirrorHandler(private val context: JavaContext) : UElementHandler() {
+ override fun visitCallExpression(node: UCallExpression) {
+ if (node.methodName != "toString") return
+ val method = node.resolve() ?: return
+ val containingClass = method.containingClass ?: return
+ if (containingClass.isInstanceOf("javax.lang.model.type.TypeMirror")) {
+ // Instead of calling `receiver.toString()`, call `TypeName.get(receiver).toString`
+ val fix =
+ node.receiver?.asSourceString()?.let { receiver ->
+ fix()
+ .replace()
+ .name("Use TypeName.toString")
+ .text(receiver)
+ .with("com.squareup.javapoet.TypeName.get($receiver)")
+ .reformat(true)
+ .shortenNames()
+ .autoFix()
+ .build()
+ }
+
+ val incident =
+ Incident(context)
+ .fix(fix)
+ .issue(ISSUE)
+ .location(context.getLocation(node))
+ .message("TypeMirror.toString includes annotations")
+ .scope(node)
+ context.report(incident)
+ }
+ }
+
+ /** Checks if the class is [qualifiedName] or has [qualifiedName] as a super type. */
+ private fun PsiClass.isInstanceOf(qualifiedName: String): Boolean =
+ // Recursion will stop when this hits Object, which has no [supers]
+ qualifiedName == this.qualifiedName || supers.any { it.isInstanceOf(qualifiedName) }
+ }
+
+ companion object {
+ val ISSUE =
+ Issue.create(
+ "TypeMirrorToString",
+ "Avoid using TypeMirror.toString",
+ """
+ This method includes type-use annotations in the string, which can lead to bugs
+ when comparing the type string against unannotated type string.
+
+ If you need a type string that includes annotations, you can suppress this lint.
+ """,
+ Category.CORRECTNESS,
+ 5,
+ Severity.ERROR,
+ Implementation(TypeMirrorToString::class.java, Scope.JAVA_FILE_SCOPE)
+ )
+ }
+}
diff --git a/lint-checks/src/test/java/androidx/build/lint/ArrayNullnessMigrationTest.kt b/lint-checks/src/test/java/androidx/build/lint/ArrayNullnessMigrationTest.kt
deleted file mode 100644
index 556cf75..0000000
--- a/lint-checks/src/test/java/androidx/build/lint/ArrayNullnessMigrationTest.kt
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.build.lint
-
-import com.android.tools.lint.checks.infrastructure.TestFile
-import com.android.tools.lint.checks.infrastructure.TestMode
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class ArrayNullnessMigrationTest :
- AbstractLintDetectorTest(
- useDetector = ArrayNullnessMigration(),
- useIssues = listOf(ArrayNullnessMigration.ISSUE),
- stubs = annotationStubs
- ) {
- @Test
- fun `Nullness annotation on parameter`() {
- val input =
- java(
- """
- package test.pkg;
- import androidx.annotation.NonNull;
- public class Foo {
- public void foo(@NonNull String[] arr) {}
- }
- """
- .trimIndent()
- )
-
- val expected =
- """
- src/test/pkg/Foo.java:4: Error: Nullness annotation on array will apply to element [ArrayMigration]
- public void foo(@NonNull String[] arr) {}
- ~~~~~~~~~~~~~~~~~~~~~
- 1 errors, 0 warnings
- """
- .trimIndent()
-
- val expectedFixDiffs =
- """
- Autofix for src/test/pkg/Foo.java line 4: Move annotation:
- @@ -4 +4
- - public void foo(@NonNull String[] arr) {}
- + public void foo(String @NonNull [] arr) {}
- """
- .trimIndent()
-
- runArrayNullnessTest(input, expected, expectedFixDiffs)
- }
-
- @Test
- fun `Nullness annotation on method return`() {
- val input =
- java(
- """
- package test.pkg;
- import androidx.annotation.Nullable;
- public class Foo {
- @Nullable
- public String[] foo() { return null; }
- }
- """
- .trimIndent(),
- )
-
- val expected =
- """
- src/test/pkg/Foo.java:5: Error: Nullness annotation on array will apply to element [ArrayMigration]
- public String[] foo() { return null; }
- ~~~
- 1 errors, 0 warnings
- """
- .trimIndent()
-
- val expectedFixDiffs =
- """
- Autofix for src/test/pkg/Foo.java line 5: Move annotation:
- @@ -4 +4
- - @Nullable
- - public String[] foo() { return null; }
- + public String @Nullable [] foo() { return null; }
- """
- .trimIndent()
-
- runArrayNullnessTest(input, expected, expectedFixDiffs)
- }
-
- @Test
- fun `Nullness annotation on method return and parameter`() {
- val input =
- java(
- """
- package test.pkg;
- import androidx.annotation.Nullable;
- public class Foo {
- @Nullable
- public String[] foo(@Nullable String[] arr) { return null; }
- }
- """
- .trimIndent()
- )
-
- val expected =
- """
- src/test/pkg/Foo.java:5: Error: Nullness annotation on array will apply to element [ArrayMigration]
- public String[] foo(@Nullable String[] arr) { return null; }
- ~~~
- src/test/pkg/Foo.java:5: Error: Nullness annotation on array will apply to element [ArrayMigration]
- public String[] foo(@Nullable String[] arr) { return null; }
- ~~~~~~~~~~~~~~~~~~~~~~
- 2 errors, 0 warnings
- """
- .trimIndent()
-
- val expectedFixDiffs =
- """
- Autofix for src/test/pkg/Foo.java line 5: Move annotation:
- @@ -4 +4
- - @Nullable
- - public String[] foo(@Nullable String[] arr) { return null; }
- + public String @Nullable [] foo(@Nullable String[] arr) { return null; }
- Autofix for src/test/pkg/Foo.java line 5: Move annotation:
- @@ -5 +5
- - public String[] foo(@Nullable String[] arr) { return null; }
- + public String[] foo(String @Nullable [] arr) { return null; }
- """
- .trimIndent()
-
- runArrayNullnessTest(input, expected, expectedFixDiffs)
- }
-
- @Test
- fun `Nullness annotation on field`() {
- val input =
- java(
- """
- package test.pkg;
- import androidx.annotation.Nullable;
- public class Foo {
- @Nullable public String[] foo;
- }
- """
- .trimIndent()
- )
-
- val expected =
- """
- src/test/pkg/Foo.java:4: Error: Nullness annotation on array will apply to element [ArrayMigration]
- @Nullable public String[] foo;
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 1 errors, 0 warnings
- """
- .trimIndent()
-
- val expectedFixDiffs =
- """
- Autofix for src/test/pkg/Foo.java line 4: Move annotation:
- @@ -4 +4
- - @Nullable public String[] foo;
- + public String @Nullable [] foo;
- """
- .trimIndent()
-
- runArrayNullnessTest(input, expected, expectedFixDiffs)
- }
-
- @Test
- fun `Nullness annotation on 2d array`() {
- val input =
- java(
- """
- package test.pkg;
- import androidx.annotation.Nullable;
- public class Foo {
- @Nullable public String[][] foo;
- }
- """
- .trimIndent()
- )
-
- val expected =
- """
- src/test/pkg/Foo.java:4: Error: Nullness annotation on array will apply to element [ArrayMigration]
- @Nullable public String[][] foo;
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 1 errors, 0 warnings
- """
- .trimIndent()
-
- val expectedFixDiffs =
- """
- Autofix for src/test/pkg/Foo.java line 4: Move annotation:
- @@ -4 +4
- - @Nullable public String[][] foo;
- + public String @Nullable [][] foo;
- """
- .trimIndent()
-
- runArrayNullnessTest(input, expected, expectedFixDiffs)
- }
-
- @Test
- fun `Nullness annotation on varargs`() {
- val input =
- java(
- """
- package test.pkg;
- import androidx.annotation.NonNull;
- public class Foo {
- public void foo(@NonNull String... arr) {}
- }
- """
- .trimIndent(),
- )
-
- val expected =
- """
- src/test/pkg/Foo.java:4: Error: Nullness annotation on array will apply to element [ArrayMigration]
- public void foo(@NonNull String... arr) {}
- ~~~~~~~~~~~~~~~~~~~~~~
- 1 errors, 0 warnings
- """
- .trimIndent()
-
- val expectedFixDiffs =
- """
- Autofix for src/test/pkg/Foo.java line 4: Move annotation:
- @@ -4 +4
- - public void foo(@NonNull String... arr) {}
- + public void foo(String @NonNull ... arr) {}
- """
- .trimIndent()
-
- runArrayNullnessTest(input, expected, expectedFixDiffs)
- }
-
- @Test
- fun `Nullness annotation on method return with array in comments`() {
- val input =
- java(
- """
- package test.pkg;
- import androidx.annotation.Nullable;
- public class Foo {
- /**
- * @return A String[]
- */
- @Nullable
- public String[] foo() { return null; }
- }
- """
- .trimIndent(),
- )
-
- val expected =
- """
- src/test/pkg/Foo.java:8: Error: Nullness annotation on array will apply to element [ArrayMigration]
- public String[] foo() { return null; }
- ~~~
- 1 errors, 0 warnings
- """
- .trimIndent()
-
- val expectedFixDiffs =
- """
- Autofix for src/test/pkg/Foo.java line 8: Move annotation:
- @@ -7 +7
- - @Nullable
- - public String[] foo() { return null; }
- + public String @Nullable [] foo() { return null; }
- """
- .trimIndent()
-
- runArrayNullnessTest(input, expected, expectedFixDiffs)
- }
-
- @Test
- fun `Nullness annotation on method return with annotation in comments`() {
- val input =
- java(
- """
- package test.pkg;
- import androidx.annotation.Nullable;
- public class Foo {
- /**
- * @return A @Nullable string array
- */
- @Nullable
- public String[] foo() { return null; }
- }
- """
- .trimIndent(),
- )
-
- val expected =
- """
- src/test/pkg/Foo.java:8: Error: Nullness annotation on array will apply to element [ArrayMigration]
- public String[] foo() { return null; }
- ~~~
- 1 errors, 0 warnings
- """
- .trimIndent()
-
- val expectedFixDiffs =
- """
- Autofix for src/test/pkg/Foo.java line 8: Move annotation:
- @@ -7 +7
- - @Nullable
- - public String[] foo() { return null; }
- + public String @Nullable [] foo() { return null; }
- """
- .trimIndent()
-
- runArrayNullnessTest(input, expected, expectedFixDiffs)
- }
-
- private fun runArrayNullnessTest(input: TestFile, expected: String, expectedFixDiffs: String) {
- lint()
- .files(*stubs, input)
- .skipTestModes(TestMode.WHITESPACE)
- .run()
- .expect(expected)
- .expectFixDiffs(expectedFixDiffs)
- }
-
- companion object {
- val annotationStubs =
- arrayOf(
- kotlin(
- """
- package androidx.annotation
- annotation class NonNull
- """
- ),
- kotlin(
- """
- package androidx.annotation
- annotation class Nullable
- """
- )
- )
- }
-}
diff --git a/lint-checks/src/test/java/androidx/build/lint/BanNullMarkedTest.kt b/lint-checks/src/test/java/androidx/build/lint/BanNullMarkedTest.kt
new file mode 100644
index 0000000..c16ea81
--- /dev/null
+++ b/lint-checks/src/test/java/androidx/build/lint/BanNullMarkedTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build.lint
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class BanNullMarkedTest :
+ AbstractLintDetectorTest(
+ useDetector = BanNullMarked(),
+ useIssues = listOf(BanNullMarked.ISSUE),
+ stubs = arrayOf(nullMarkedStub)
+ ) {
+ @Test
+ fun `Usage of NullMarked in a package-info file`() {
+ val input =
+ java(
+ """
+ @NullMarked
+ package test.pkg;
+
+ import org.jspecify.annotations.NullMarked;
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/package-info.java:1: Error: Should not use @NullMarked annotation [BanNullMarked]
+ @NullMarked
+ ~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ check(input).expect(expected)
+ }
+
+ @Test
+ fun `Usage of NullMarked on a class`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+
+ import org.jspecify.annotations.NullMarked;
+
+ @NullMarked
+ public class Foo {}
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:5: Error: Should not use @NullMarked annotation [BanNullMarked]
+ @NullMarked
+ ~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ check(input).expect(expected)
+ }
+
+ @Test
+ fun `Usage of NullMarked on a method`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+
+ import org.jspecify.annotations.NullMarked;
+
+ public class Foo {
+ @NullMarked
+ public void foo() {}
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:6: Error: Should not use @NullMarked annotation [BanNullMarked]
+ @NullMarked
+ ~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ check(input).expect(expected)
+ }
+
+ @Test
+ fun `Usage of NullMarked on a constructor`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+
+ import org.jspecify.annotations.NullMarked;
+
+ public class Foo {
+ @NullMarked
+ public Foo() {}
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:6: Error: Should not use @NullMarked annotation [BanNullMarked]
+ @NullMarked
+ ~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ check(input).expect(expected)
+ }
+
+ companion object {
+ private val nullMarkedStub =
+ java(
+ """
+ package org.jspecify.annotations;
+
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+
+ @Target({ElementType.MODULE, ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
+ public @interface NullMarked {}
+ """
+ .trimIndent()
+ )
+ }
+}
diff --git a/lint-checks/src/test/java/androidx/build/lint/JSpecifyNullnessMigrationTest.kt b/lint-checks/src/test/java/androidx/build/lint/JSpecifyNullnessMigrationTest.kt
new file mode 100644
index 0000000..2b91a0a
--- /dev/null
+++ b/lint-checks/src/test/java/androidx/build/lint/JSpecifyNullnessMigrationTest.kt
@@ -0,0 +1,790 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestMode
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class JSpecifyNullnessMigrationTest :
+ AbstractLintDetectorTest(
+ useDetector = JSpecifyNullnessMigration(),
+ useIssues = listOf(JSpecifyNullnessMigration.ISSUE),
+ stubs = annotationStubs
+ ) {
+ @Test
+ fun `Nullness annotation on array parameter`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.NonNull;
+ public class Foo {
+ public void foo(@NonNull String[] arr) {}
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ public void foo(@NonNull String[] arr) {}
+ ~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Move annotation:
+ @@ -4 +4
+ - public void foo(@NonNull String[] arr) {}
+ + public void foo(String @NonNull [] arr) {}
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on array method return`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ @Nullable
+ public String[] foo() { return null; }
+ }
+ """
+ .trimIndent(),
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Move annotation:
+ @@ -4 +4
+ - @Nullable
+ - public String[] foo() { return null; }
+ + public String @Nullable [] foo() { return null; }
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on array method return and array parameter`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ @Nullable
+ public String[] foo(@Nullable String[] arr) { return null; }
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable
+ ~~~~~~~~~
+ src/test/pkg/Foo.java:5: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ public String[] foo(@Nullable String[] arr) { return null; }
+ ~~~~~~~~~
+ 2 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Move annotation:
+ @@ -4 +4
+ - @Nullable
+ - public String[] foo(@Nullable String[] arr) { return null; }
+ + public String @Nullable [] foo(@Nullable String[] arr) { return null; }
+ Autofix for src/test/pkg/Foo.java line 5: Move annotation:
+ @@ -5 +5
+ - public String[] foo(@Nullable String[] arr) { return null; }
+ + public String[] foo(String @Nullable [] arr) { return null; }
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on array field`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ @Nullable public String[] foo;
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable public String[] foo;
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Move annotation:
+ @@ -4 +4
+ - @Nullable public String[] foo;
+ + public String @Nullable [] foo;
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on 2d array`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ @Nullable public String[][] foo;
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable public String[][] foo;
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Move annotation:
+ @@ -4 +4
+ - @Nullable public String[][] foo;
+ + public String @Nullable [][] foo;
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on varargs`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.NonNull;
+ public class Foo {
+ public void foo(@NonNull String... arr) {}
+ }
+ """
+ .trimIndent(),
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ public void foo(@NonNull String... arr) {}
+ ~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Move annotation:
+ @@ -4 +4
+ - public void foo(@NonNull String... arr) {}
+ + public void foo(String @NonNull ... arr) {}
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on array varargs`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.NonNull;
+ public class Foo {
+ public void foo(@NonNull String[]... args) {}
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ public void foo(@NonNull String[]... args) {}
+ ~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Move annotation:
+ @@ -4 +4
+ - public void foo(@NonNull String[]... args) {}
+ + public void foo(String @NonNull []... args) {}
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on method return with array in comments`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ /**
+ * @return A String[]
+ */
+ @Nullable
+ public String[] foo() { return null; }
+ }
+ """
+ .trimIndent(),
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:7: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 7: Move annotation:
+ @@ -7 +7
+ - @Nullable
+ - public String[] foo() { return null; }
+ + public String @Nullable [] foo() { return null; }
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on method return with annotation in comments`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ /**
+ * @return A @Nullable string array
+ */
+ @Nullable
+ public String[] foo() { return null; }
+ }
+ """
+ .trimIndent(),
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:7: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 7: Move annotation:
+ @@ -7 +7
+ - @Nullable
+ - public String[] foo() { return null; }
+ + public String @Nullable [] foo() { return null; }
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation removed from local variable declaration`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ public String foo() {
+ @Nullable String str = null;
+ return str;
+ }
+ }
+ """
+ .trimIndent()
+ )
+ val expected =
+ """
+ src/test/pkg/Foo.java:5: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable String str = null;
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 5: Delete:
+ @@ -5 +5
+ - @Nullable String str = null;
+ + String str = null;
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation removed from void return type`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ @Nullable
+ public void foo() {}
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Delete:
+ @@ -4 +4
+ - @Nullable
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation removed from primitive parameter type`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ public void foo(@Nullable int i) {}
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ public void foo(@Nullable int i) {}
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Delete:
+ @@ -4 +4
+ - public void foo(@Nullable int i) {}
+ + public void foo(int i) {}
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on class type parameter`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.NonNull;
+ public class Foo {
+ public void foo(@NonNull Foo.InnerFoo arr) {}
+ public class InnerFoo {}
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ public void foo(@NonNull Foo.InnerFoo arr) {}
+ ~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Move annotation:
+ @@ -4 +4
+ - public void foo(@NonNull Foo.InnerFoo arr) {}
+ + public void foo(Foo.@NonNull InnerFoo arr) {}
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on class type return`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ @Nullable
+ public String foo() { return null; }
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Move annotation:
+ @@ -4 +4
+ - @Nullable
+ - public String foo() { return null; }
+ + public @Nullable String foo() { return null; }
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on class type param`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ public String foo(@Nullable String foo) { return null; }
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ public String foo(@Nullable String foo) { return null; }
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs = ""
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on class type param and return`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.NonNull;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ @Nullable
+ public String foo(@NonNull String foo) { return null; }
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:5: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable
+ ~~~~~~~~~
+ src/test/pkg/Foo.java:6: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ public String foo(@NonNull String foo) { return null; }
+ ~~~~~~~~
+ 2 errors, 0 warnings
+ """
+ .trimIndent()
+
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 5: Move annotation:
+ @@ -5 +5
+ - @Nullable
+ - public String foo(@NonNull String foo) { return null; }
+ + public @Nullable String foo(@NonNull String foo) { return null; }
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on type parameter return`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class Foo {
+ @Nullable
+ public <T> T foo() {
+ return null;
+ }
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 4: Move annotation:
+ @@ -4 +4
+ - @Nullable
+ - public <T> T foo() {
+ + public <T> @Nullable T foo() {
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on inner type where outer type contains name`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ public class RecyclerView {
+ public class Recycler {}
+ @Nullable
+ public RecyclerView.Recycler foo() {
+ return null;
+ }
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/RecyclerView.java:5: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/RecyclerView.java line 5: Move annotation:
+ @@ -5 +5
+ - @Nullable
+ - public RecyclerView.Recycler foo() {
+ + public RecyclerView.@Nullable Recycler foo() {
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on parameterized type`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.Nullable;
+ import java.util.List;
+ public class Foo {
+ @Nullable
+ public List<String> foo() {
+ return null;
+ }
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:5: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ @Nullable
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+ val expectedFixDiffs =
+ """
+ Autofix for src/test/pkg/Foo.java line 5: Move annotation:
+ @@ -5 +5
+ - @Nullable
+ - public List<String> foo() {
+ + public @Nullable List<String> foo() {
+ """
+ .trimIndent()
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ @Test
+ fun `Nullness annotation on parameter with newline after type`() {
+ val input =
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.NonNull;
+ public class Foo {
+ public void foo(@NonNull String
+ arg) {}
+ }
+ """
+ .trimIndent()
+ )
+
+ val expected =
+ """
+ src/test/pkg/Foo.java:4: Error: Switch nullness annotation to JSpecify [JSpecifyNullness]
+ public void foo(@NonNull String
+ ~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+ val expectedFixDiffs = ""
+
+ runNullnessTest(input, expected, expectedFixDiffs)
+ }
+
+ private fun runNullnessTest(input: TestFile, expected: String, expectedFixDiffs: String) {
+ lint()
+ .files(*stubs, input)
+ // Skip WHITESPACE mode because an array suffix with whitespace in the middle "[ ]" will
+ // break the pattern matching, but is extremely unlikely to happen in practice.
+ // Skip FULLY_QUALIFIED mode because the type-use annotation positioning depends on
+ // whether types are fully qualified, so fixes will be different.
+ .skipTestModes(TestMode.WHITESPACE, TestMode.FULLY_QUALIFIED)
+ .run()
+ .expect(expected)
+ .expectFixDiffs(expectedFixDiffs)
+ }
+
+ companion object {
+ val annotationStubs =
+ arrayOf(
+ kotlin(
+ """
+ package androidx.annotation
+ annotation class NonNull
+ """
+ ),
+ kotlin(
+ """
+ package androidx.annotation
+ annotation class Nullable
+ """
+ )
+ )
+ }
+}
diff --git a/lint-checks/src/test/java/androidx/build/lint/TypeMirrorToStringTest.kt b/lint-checks/src/test/java/androidx/build/lint/TypeMirrorToStringTest.kt
new file mode 100644
index 0000000..ced7240
--- /dev/null
+++ b/lint-checks/src/test/java/androidx/build/lint/TypeMirrorToStringTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build.lint
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class TypeMirrorToStringTest :
+ AbstractLintDetectorTest(
+ useDetector = TypeMirrorToString(),
+ useIssues = listOf(TypeMirrorToString.ISSUE),
+ ) {
+ @Test
+ fun `Test usage TypeMirror#toString on simple receiver`() {
+ val input =
+ arrayOf(
+ java(
+ """
+ package androidx.test;
+ import javax.lang.model.type.TypeMirror;
+ public class Foo {
+ public String getStringForType(TypeMirror tm) {
+ return tm.toString();
+ }
+ }
+ """
+ .trimIndent()
+ )
+ )
+ val expected =
+ """
+ src/androidx/test/Foo.java:5: Error: TypeMirror.toString includes annotations [TypeMirrorToString]
+ return tm.toString();
+ ~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+ val expectedFixDiffs =
+ """
+ Autofix for src/androidx/test/Foo.java line 5: Use TypeName.toString:
+ @@ -2 +2
+ + import com.squareup.javapoet.TypeName;
+ @@ -5 +6
+ - return tm.toString();
+ + return TypeName.get(tm).toString();
+ """
+ .trimIndent()
+
+ check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
+ }
+
+ @Test
+ fun `Test usage of TypeMirror#toString on method call receiver`() {
+ val input =
+ arrayOf(
+ java(
+ """
+ package androidx.test;
+ import javax.lang.model.type.TypeMirror;
+ public class Foo {
+ public TypeMirror getMirror() {
+ return null;
+ }
+ public String getStringForType() {
+ return getMirror().toString();
+ }
+ }
+ """
+ .trimIndent()
+ )
+ )
+ val expected =
+ """
+ src/androidx/test/Foo.java:8: Error: TypeMirror.toString includes annotations [TypeMirrorToString]
+ return getMirror().toString();
+ ~~~~~~~~~~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+ val expectedFixDiffs =
+ """
+ Autofix for src/androidx/test/Foo.java line 8: Use TypeName.toString:
+ @@ -2 +2
+ + import com.squareup.javapoet.TypeName;
+ @@ -8 +9
+ - return getMirror().toString();
+ + return TypeName.get(getMirror()).toString();
+ """
+ .trimIndent()
+
+ check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
+ }
+}
diff --git a/lint/lint-gradle/build.gradle b/lint/lint-gradle/build.gradle
index e578727..2ac0206 100644
--- a/lint/lint-gradle/build.gradle
+++ b/lint/lint-gradle/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import androidx.build.Publish
@@ -45,4 +47,5 @@
publish = Publish.SNAPSHOT_AND_RELEASE
inceptionYear = "2024"
description = "Lint checks to verify usage of Gradle APIs."
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/mediarouter/mediarouter/src/main/res/values-in/strings.xml b/mediarouter/mediarouter/src/main/res/values-in/strings.xml
index a646d3d..87abce1 100644
--- a/mediarouter/mediarouter/src/main/res/values-in/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-in/strings.xml
@@ -25,7 +25,7 @@
<string name="mr_chooser_title" msgid="1419936397646839840">"Transmisikan ke"</string>
<string name="mr_chooser_searching" msgid="6114250663023140921">"Mencari perangkat"</string>
<string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Mencari perangkat..."</string>
- <string name="mr_controller_disconnect" msgid="7812275474138309497">"Putuskan koneksi"</string>
+ <string name="mr_controller_disconnect" msgid="7812275474138309497">"Berhenti hubungkan"</string>
<string name="mr_controller_stop_casting" msgid="804210341192624074">"Hentikan transmisi"</string>
<string name="mr_controller_close_description" msgid="5684434439232634509">"Tutup"</string>
<string name="mr_controller_play" msgid="1253345086594430054">"Putar"</string>
diff --git a/metrics/metrics-performance/src/androidTest/java/androidx/metrics/performance/test/JankStatsTest.kt b/metrics/metrics-performance/src/androidTest/java/androidx/metrics/performance/test/JankStatsTest.kt
index 095938c..f063877 100644
--- a/metrics/metrics-performance/src/androidTest/java/androidx/metrics/performance/test/JankStatsTest.kt
+++ b/metrics/metrics-performance/src/androidTest/java/androidx/metrics/performance/test/JankStatsTest.kt
@@ -15,6 +15,7 @@
*/
package androidx.metrics.performance.test
+import android.os.Build.VERSION.SDK_INT
import android.view.Choreographer
import androidx.metrics.performance.FrameData
import androidx.metrics.performance.FrameDataApi24
@@ -36,6 +37,7 @@
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -107,6 +109,7 @@
@Test
fun testEnable() {
+ assumeTrue("Skip running an API 26 as it is flaky b/361092826", SDK_INT != 26)
assertTrue(jankStats.isTrackingEnabled)
jankStats.isTrackingEnabled = false
assertFalse(jankStats.isTrackingEnabled)
diff --git a/navigation/navigation-common/build.gradle b/navigation/navigation-common/build.gradle
index 7921162..bcde5af 100644
--- a/navigation/navigation-common/build.gradle
+++ b/navigation/navigation-common/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -78,4 +80,5 @@
inceptionYear = "2017"
description = "Android Navigation-Common"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/NavTypeConverter.kt b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/NavTypeConverter.kt
index c2cf3c6..df00b37 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/NavTypeConverter.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/NavTypeConverter.kt
@@ -147,10 +147,14 @@
return Class.forName(className)
} catch (_: ClassNotFoundException) {}
}
- throw IllegalArgumentException(
+ var errorMsg =
"Cannot find class with name \"$serialName\". Ensure that the " +
"serialName for this argument is the default fully qualified name"
- )
+ if (kind is SerialKind.ENUM) {
+ errorMsg =
+ "$errorMsg.\nIf the build is minified, try annotating the Enum class with \"androidx.annotation.Keep\" to ensure the Enum is not removed."
+ }
+ throw IllegalArgumentException(errorMsg)
}
/**
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/serialization/NavArgumentGeneratorTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/serialization/NavArgumentGeneratorTest.kt
index 0b7db49..f395dd1 100644
--- a/navigation/navigation-common/src/test/java/androidx/navigation/serialization/NavArgumentGeneratorTest.kt
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/serialization/NavArgumentGeneratorTest.kt
@@ -825,7 +825,9 @@
assertThat(exception.message)
.isEqualTo(
"Cannot find class with name \"MyCustomSerialName\". Ensure that the " +
- "serialName for this argument is the default fully qualified name"
+ "serialName for this argument is the default fully qualified name." +
+ "\nIf the build is minified, try annotating the Enum class " +
+ "with \"androidx.annotation.Keep\" to ensure the Enum is not removed."
)
}
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index 9fa7d67..0088065 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -68,6 +70,7 @@
legacyDisableKotlinStrictApiMode = true
samples(projectOrArtifact(":navigation:navigation-compose:navigation-compose-samples"))
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
android {
diff --git a/navigation/navigation-compose/samples/build.gradle b/navigation/navigation-compose/samples/build.gradle
index 19e494b..b672a68 100644
--- a/navigation/navigation-compose/samples/build.gradle
+++ b/navigation/navigation-compose/samples/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@@ -52,6 +54,7 @@
type = LibraryType.SAMPLES
inceptionYear = "2020"
description = "Samples for Compose integration with Navigation"
+ kotlinTarget = KotlinTarget.KOTLIN_2_0
}
tasks.withType(KotlinCompile).configureEach {
diff --git a/navigation/navigation-dynamic-features-fragment/build.gradle b/navigation/navigation-dynamic-features-fragment/build.gradle
index a1527d203..34a65f65 100644
--- a/navigation/navigation-dynamic-features-fragment/build.gradle
+++ b/navigation/navigation-dynamic-features-fragment/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@@ -68,4 +70,5 @@
inceptionYear = "2019"
description = "Android Dynamic Feature Navigation Fragment"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/navigation/navigation-fragment-compose/build.gradle b/navigation/navigation-fragment-compose/build.gradle
index 9b1cc7b..415e042 100644
--- a/navigation/navigation-fragment-compose/build.gradle
+++ b/navigation/navigation-fragment-compose/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -56,4 +58,5 @@
inceptionYear = "2024"
description = "Add Compose destinations to Navigation with Fragments"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/navigation/navigation-fragment/build.gradle b/navigation/navigation-fragment/build.gradle
index b20d87a..6c5a89a 100644
--- a/navigation/navigation-fragment/build.gradle
+++ b/navigation/navigation-fragment/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -57,6 +59,7 @@
inceptionYear = "2017"
description = "Android Navigation-Fragment"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
android {
diff --git a/navigation/navigation-lint-common/build.gradle b/navigation/navigation-lint-common/build.gradle
index 4ed49c5..192dd51 100644
--- a/navigation/navigation-lint-common/build.gradle
+++ b/navigation/navigation-lint-common/build.gradle
@@ -31,6 +31,8 @@
dependencies {
compileOnly(libs.androidLintMinApi)
compileOnly(libs.kotlinStdlib)
+ compileOnly(libs.androidLintTests)
+ compileOnly(libs.junit)
}
androidx {
diff --git a/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/LintUtil.kt b/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/LintUtil.kt
index 611f180..85d67c3 100644
--- a/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/LintUtil.kt
+++ b/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/LintUtil.kt
@@ -16,6 +16,11 @@
package androidx.navigation.lint.common
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import java.util.Locale
+import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
@@ -66,3 +71,60 @@
symbol.classKind == KtClassKind.COMPANION_OBJECT) to symbol.name?.asString()
}
}
+
+/**
+ * Copied from compose.lint.test.stubs
+ *
+ * Utility for creating a [kotlin] and corresponding [bytecode] stub, to try and make it easier to
+ * configure everything correctly.
+ *
+ * @param filename name of the Kotlin source file, with extension - e.g. "Test.kt". These should be
+ * unique across a test.
+ * @param filepath directory structure matching the package name of the Kotlin source file. E.g. if
+ * the source has `package foo.bar`, this should be `foo/bar`. If this does _not_ match, lint will
+ * not be able to match the generated classes with the source file, and so won't print them to
+ * console.
+ * @param source Kotlin source for the bytecode
+ * @param bytecode generated bytecode that will be used in tests. Leave empty to generate the
+ * bytecode for [source].
+ * @return a pair of kotlin test file, to bytecode test file
+ */
+fun kotlinAndBytecodeStub(
+ filename: String,
+ filepath: String,
+ checksum: Long,
+ @Language("kotlin") source: String,
+ vararg bytecode: String
+): KotlinAndBytecodeStub {
+ val filenameWithoutExtension = filename.substringBefore(".").lowercase(Locale.ROOT)
+ val kotlin = kotlin(source).to("$filepath/$filename")
+ val bytecodeStub =
+ TestFiles.bytecode("libs/$filenameWithoutExtension.jar", kotlin, checksum, *bytecode)
+ return KotlinAndBytecodeStub(kotlin, bytecodeStub)
+}
+
+class KotlinAndBytecodeStub(val kotlin: TestFile, val bytecode: TestFile)
+
+/**
+ * Copied from compose.lint.test.stubs
+ *
+ * Utility for creating a [bytecode] stub, to try and make it easier to configure everything
+ * correctly.
+ *
+ * @param filename name of the Kotlin source file, with extension - e.g. "Test.kt". These should be
+ * unique across a test.
+ * @param filepath directory structure matching the package name of the Kotlin source file. E.g. if
+ * the source has `package foo.bar`, this should be `foo/bar`. If this does _not_ match, lint will
+ * not be able to match the generated classes with the source file, and so won't print them to
+ * console.
+ * @param source Kotlin source for the bytecode
+ * @param bytecode generated bytecode that will be used in tests. Leave empty to generate the
+ * bytecode for [source].
+ */
+fun bytecodeStub(
+ filename: String,
+ filepath: String,
+ checksum: Long,
+ @Language("kotlin") source: String,
+ vararg bytecode: String
+): TestFile = kotlinAndBytecodeStub(filename, filepath, checksum, source, *bytecode).bytecode
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/Stub.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/Stub.kt
index 4b568a9..89df0cd 100644
--- a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/Stub.kt
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/Stub.kt
@@ -16,8 +16,244 @@
package androidx.navigation.runtime.lint
-val TEST_CLASS =
- """
+import androidx.navigation.lint.common.bytecodeStub
+import androidx.navigation.lint.common.kotlinAndBytecodeStub
+
+internal val NAV_CONTROLLER =
+ bytecodeStub(
+ "NavController.kt",
+ "androidx/navigation",
+ 0x40e8c1a8,
+ """
+package androidx.navigation
+
+import kotlin.reflect.KClass
+
+open class NavController {
+
+ fun navigate(resId: Int) {}
+
+ fun navigate(route: String) {}
+
+ fun <T : Any> navigate(route: T) {}
+}
+
+inline fun NavController.createGraph(
+ startDestination: Any,
+ route: KClass<*>? = null,
+): NavGraph { return NavGraph() }
+""",
+ """
+META-INF/main.kotlin_module:
+H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgMuQSTsxLKcrPTKnQy0ssy0xPLMnM
+zxPi90ssc87PKynKz8lJLfIuEeIECnjkF5d4l3CJcnEn5+fqpVYk5hbkpAqx
+haSChJUYtBgAOMX57WIAAAA=
+""",
+ """
+androidx/navigation/NavController.class:
+H4sIAAAAAAAA/41SW28SQRT+ZoFl2dIW0NYWW7UXLW21Sxt9kaaJNjFug2gs
+4aVPA2zowDKb7A6kj/wW/4FPGh9M46M/ynhmIb1qdJM99++bc2bOz1/fvgN4
+jj2GFS7bYSDaZ47kQ9HhSgTSqfHhYSBVGPi+F6bBGHJdPuSOz2XHed/sei2V
+RoLB3BdSqAOGRGmzkUUKpo0k0gxJdSoihrXqP9krDNYk5xGu5G42GFKhF7lt
+BuYyzJWql2cfq1DITkXXrFWDsON0PdUMuZCRw6UMVHxA5NQCVRv4fkUzBQPl
+WcgzPOgFyhfS6Q77jpDKCyX3HVdqxki0ojTu0GGtU6/Vm8A/8JD3PSpk2Lja
+xPgCKn9qK4s5zNu4i3sMhdsFN6aZEOlplvbrL29nDkr1epwu3M4x5KuTid55
+ire54hQz+sMEPS3TIqMF6BZ7FD8T2iuT1d5lCM9HS7axYNhG7nxkG5Y26LeS
+pC36Z6z5hfPRnlFmr1M/PpmUPFrOJYpGOblqWeejXGqLUntmziwab8cF6aOZ
+cQFFLdKZKz5VlW19Mu2b7qdO+3RtCXZ6it7+MGjTCsxWhfRqg37TC+u86Xt6
++KDF/QYPhfYnwfWPA6lE33PlUESCQhev9epyERgyx6IjuRqEBLGPg0HY8t4I
+jV+c4Btj9BUQVmDQFusvSd3SUpPcJs/RvZNObX2B9ZkMA09JmnHQxDOS2XEB
+MrBJ5zEVRzT4RVxP498EWjFwfpycALU1jRmSmmKWcpqiQlpXpbcLha9YuE5k
+EvCSKH1BlCaKRcrvaFv3n5s0VkTif1izf2VdorwTV9+/zm6gHMst7JKuUnSZ
+ruTBCRIuHrp45NINr5KJNRfreHwCFuEJNk4wFcGOUIpgRtomYzNCPkIxwnTs
+ln4DameR7LoEAAA=
+""",
+ """
+androidx/navigation/NavControllerKt.class:
+H4sIAAAAAAAA/61TbU8TQRB+9o62RxUoRSqv9YWqgC9XKr6WEA0GvVjQiCEx
+JJqlXcrS6x252zYmJuon/4P/wm8aTQzxoz/KOHseCCLiBz7s7Mzs7DPPzM5+
+//H5K4BpzDCMca8W+LL20vZ4W9a5kr5nL/L2nO+pwHddETxUKTCGzAZvc9vl
+Xt1+tLohquQ1GY5VA8GVuB/wzXUGd7xyKFy58idQudLwlSs9OxBrLtn2wzmX
+h2F54iCwKFuZwT/KdDOTs4dnHKv4Qd3eEGo14NILbe55voqiQnvRV4st16Wo
+wr+iKISvuoLCkjNqXYazFtIM+ZjTRrtpS0+JwOOu7VARdF9WwxSOM/RX10W1
+Ead5zAPeFBTIcGH8LzX+9ixpkHp5Yvk4utGTRhcy9Jqh4oG6J0IlvYiZhSzD
+yL/KT+GE5iw9qWYZzHENmMPJNPoxQIAFWVgr7JkG5jD0FnSNe/1j//FqDNn9
+RTEkAr+lBMPJA0aGoW9XqkJNrPGWqxjeHOlgOvsjD52ckX2NeNEqTe8Q7N1O
+tSAUr3HF6YrRbJv0TZkWnVqAetrQikGHL6XWiqTVphjmt97m0ltv08aAES2t
+Zkj8cg2dI33IKLJJo2iUkhmT9I5St2VkEkNW1rDMAVZMPvj2ztJoJZrGw6oh
+Jpk9zbvSoCI65vwaPU5PRXpisdVcFcFTPer6Lf0qd5d5ILUdOzuXZJ1mrxWQ
+Pvyk5SnZFI7XlqGk47u/vwz9pz9Pd4Z/T1jXkuLVxgLfjBOkl/xWUBXzUhuD
+McbyPnxMwUCHbi/tg0ggSdZVsp7H/txk9tgn9F7M9pE0Z7+g/9lHDH6I4qdJ
+Jqkb3cjiGumTdKMbFoYwDP0+OYxgNMLOUUSeIrV2Cqfp7vUIIYUbMYZF+01a
+fWZsbMtOINOJMzhLuib2iq4laM+PJl6/R4ItHEjQxK1IMitimo3q6aFsvQTd
+E3HaZp3bxTqPsZh1fod1PmZt4HbEu4Qy7XforEBkzq3AdHDewQUH45hwCPKi
+g0u4vAIW4grsFaRCJEIUQ4yGyFLXQ5wKcfonI/JCLIwGAAA=
+"""
+ )
+
+internal val NAV_DESTINATION =
+ bytecodeStub(
+ "NavDestination.kt",
+ "androidx/navigation",
+ 0x89e4229a,
+ """
+package androidx.navigation
+
+open class NavDestination
+""",
+ """
+META-INF/main.kotlin_module:
+H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgMuQSTsxLKcrPTKnQy0ssy0xPLMnM
+zxPi90ssc87PKynKz8lJLfIuEeIECnjkF5d4l3CJcnEn5+fqpVYk5hbkpAqx
+haSChJUYtBgAOMX57WIAAAA=
+""",
+ """
+androidx/navigation/NavDestination.class:
+H4sIAAAAAAAA/4VRO08CQRD+ZoEDTpSHiuAjUWOhFh4SO42Jj5iQICZqaKwW
+7oLLYy/hFkLJb/EfWJlYGGLpjzLOnTRWNl++x+zMZPbr+/0DwAm2CLtSu0Nf
+uRNHy7HqSKN87TTk+NoLjNKRTIIIua4cS6cvdce5a3W9tkkiRrDOlFbmnBDb
+P2hmkIBlI44kIW6eVUDYq//f/pSQr/d801faufWMdKWR7InBOMZLUgjpEECg
+HvsTFaoKM/eYsD2b2rYoCVvkmM2mqWJpNq2KCl0mPl8skRNhXZXC1/m/c496
+hve88l2PkK0r7TVGg5Y3fJStPjuFut+W/aYcqlDPTfvBHw3b3o0KRfl+pI0a
+eE0VKE4vtPZN1DjADgSfYb5zeBXGEisn0kDi8A2pVyYCZUYrMuNYZ8z8FiAN
+O8o3IlzDZvRhhAXOMk+I1bBYw1INWeSYIl9DActPoAArWOU8gB2gGMD6AQ9d
+W4PtAQAA
+"""
+ )
+
+internal val NAV_GRAPH =
+ bytecodeStub(
+ "NavGraph.kt",
+ "androidx/navigation",
+ 0x54a108f5,
+ """
+package androidx.navigation
+
+open class NavGraph: NavDestination() {
+ fun <T : Any> setStartDestination(startDestRoute: T) {}
+}
+""",
+ """
+META-INF/main.kotlin_module:
+H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgMuQSTsxLKcrPTKnQy0ssy0xPLMnM
+zxPi90ssc87PKynKz8lJLfIuEeIECnjkF5d4l3CJcnEn5+fqpVYk5hbkpAqx
+haSChJUYtBgAOMX57WIAAAA=
+""",
+ """
+androidx/navigation/NavGraph.class:
+H4sIAAAAAAAA/31SXU8TQRQ9s2232wVpAUGo4AegFFC2Ep8sIfEjaE2tSpu+
+8DRtN2X6MWt2pg2P/S3+A580PpjGR3+U8c62aA3iJnPvnXPvuXNm7/z4+fUb
+gMfYZ1jjshkGonnuST4QLa5FIL0yH7wM+YezJBjDxhUVL3ylhYy2ScQY7EMh
+hT5iiOV2arNIwHYRR5Ihrs+EYrhV+t9RBYZF5euK5qGe6sywlCu1+YB7XS5b
+3tt622/owk6NhB9Wn1zOHOWq1Si9WQrCltf2dT3kQiqPSxnoqKXyyoEu97td
+OnJOXZx3EvS17yBNOjuB7grptQc9T0jth5J3vaLUIbURDZXEPIlqnPmNzqTP
+Ox7ynk+FDNv/EDuFVEyTVsH8nkVcd7GAJYaFyxSG+dJExRtf8ybXnDCrN4jR
+2JgxKWPAwDqEnwuzy1PUfMTwfjTMutaKNV4OrYzljobkjHEsZ3llNDyw8uxZ
+4vtHm5Kv1zOxrJWPbzjOaJhJ7Fp5+8DOJLPWq3GBYxofMGxdNcCpeZFMo6rK
+MHMx2f2OpjfwPGj6DOmSkH6536v7YZXXu765fdDg3RoPhdlPwFRFtKhfP6R4
+66Qvtej5RTkQSlD69+9++mekDG4l6IcN/1gY/uqEUxszpgpxFxa9SvNZJJQe
+Kdlt2nlGNvnE7mc4n6J0jqwdgXHskJ0dFyAFl/w8ZgiJReQCVVvkk3sLmS9Y
+/ptuE8XQl8clE7qJ0rhB+d2o+hr2DGZEzEXAg8jex0Pyx4SuUJvVU8SKyBZx
+s4g1rFOIW0Xcxp1TMHO1jVOkFFyFTQVbYUZhS+FeZNMKs78AJaVXJ/gDAAA=
+"""
+ )
+
+internal val NAV_HOST =
+ bytecodeStub(
+ "NavHost.kt",
+ "androidx/navigation",
+ 0x5ce5aeda,
+ """
+package androidx.navigation
+
+import kotlin.reflect.KClass
+
+interface NavHost
+
+inline fun NavHost.createGraph(
+ startDestination: Any,
+ route: KClass<*>? = null,
+): NavGraph { return NavGraph() }
+""",
+ """
+META-INF/main.kotlin_module:
+H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgMuQSTsxLKcrPTKnQy0ssy0xPLMnM
+zxPi90ssc87PKynKz8lJLfIuEeIECnjkF5d4l3CJcnEn5+fqpVYk5hbkpAqx
+haSChJUYtBgAOMX57WIAAAA=
+""",
+ """
+androidx/navigation/NavHost.class:
+H4sIAAAAAAAA/32OzUoDMRSFz81of8a/qVqoiK9g2uLOlZviQFVQcDOrtBNL
+OtMEmnToss/lQrr2oaR3qmsTOPecc+FLvn8+vwDcoUu4VjZfOpOvpVWVmalg
+nJXPqnp0PjRBhGSuKiVLZWfyZTLXU24jQmdcuFAaK590ULkK6p4gFlXEWKql
+XQsIVHC/NnXqs8sHhO5204pFT8QiYffR226Gok/1cki4Gf/zH36DkfFfui0C
+hze3Wk71yJSacPW6ssEs9LvxZlLqB2td2AN8g/E4wO8RuNjrOS55Dhh5yLeR
+IUrRTNFK0UbMFkcpjnGSgTxOcZZBeCQenR1YLcIOPwEAAA==
+""",
+ """
+androidx/navigation/NavHostKt.class:
+H4sIAAAAAAAA/61TbU8TQRB+9o62R1EsRSogrW9VAV+uVnwtIRqNerGgEUNi
+SDRLu5Sl1ztzu200JsZP/gf/hd80mhjjR3+UcfY8QUTgix92dmZ25plndme/
+//j0BcAMZhmKPGhGoWy+cAPeky2uZRi4C7x3L1T6vs6AMeTWeY+7Pg9a7oOV
+ddEgr80w0IgE1+JuxJ+vMaxO1ncBqtX/hqjV26H2ZeBGYtUn271/y+dK1aZ2
+gonr1Bjk/yk0Oz23d60T9TBquetCr0RcBsrlQRDqOEq5C6Fe6Po+RZV3i6IQ
+vuILCkvP6jWp5hxkGUoJp/Vex5WBFlHAfdcLdET5sqEy2Mcw0lgTjXZS5iGP
+eEdQIMPpyX/0uOlZNCCt2tTSPgziQBb7kaMXVJpH+rZQWgYxMwd5hond2s/g
+oOEsA6nnGOxJA1jAoSxGMEqAZVleLW+ZAOYxDJVNj1v9xV3fiyG/vR2GVBR2
+tWA4tMOYMAz/UaTcFKu862uGl/9pDL3tkXtOy8S25p91qzMb1IZ+l5oXmje5
+5pRidXo2fURmRL8RoHtsG8WiwxfSaBXSmhcY7nx9U8h+fZO1Rq14GTVH4pdr
+/CTp41aFTVsVq5rO2aT3VQcdK5cad/KWY4+ySvret7eOQavSBO7VDTHJJtd2
+vk30+26FTXqQA3UZiIVuZ0VEj81gm/cLG9xf4pE0duLsX5QtmrRuRPrhR91A
+y47wgp5Uko5vbn4Q+j1/n26M+paw/YuaN9rz/HlSILsYdqOGuCONMZZgLG3D
+xwVY6DMXS/sYUkiTdZGsp4m/MJ0f+IihM/lhkvbcZ4w8+YCx93H8DMk03cMg
+BnCJ9GnKGISDcRyGeZkCJlCMsQvIo0SRRjuCo5R7OUbI4EqC4dB+ldawnRi/
+ZT+Q68cxHCfdEHtFaSnaS8XU63dIsfkdCdq4FkvmxEzzcT8OVcuSdIjJJuvC
+H6xLOJGwLm2wLiWsLVyPeVdRo/0GnZWJzMll2B5OeTjtYRJTHkGe8XAW55bB
+FM7DXUZGIaVQUSgq5OnWFY4oHP0JmQ7w4mgGAAA=
+"""
+ )
+
+internal val TEST_NAV_HOST =
+ bytecodeStub(
+ "TestNavHost.kt",
+ "androidx/navigation",
+ 0x2f602e26,
+ """
+package androidx.navigation
+
+class TestNavHost: NavHost
+""",
+ """
+META-INF/main.kotlin_module:
+H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgMuQSTsxLKcrPTKnQy0ssy0xPLMnM
+zxPi90ssc87PKynKz8lJLfIuEeIECnjkF5d4l3CJcnEn5+fqpVYk5hbkpAqx
+haSChJUYtBgAOMX57WIAAAA=
+""",
+ """
+androidx/navigation/TestNavHost.class:
+H4sIAAAAAAAA/4VRTUtCQRQ99z01fVmpfWlW0q5a9EzaFUEFkWAGJW5cjb5H
+Teo8cEZp6W/pH7QKWoS07EdF9z2jWgQt5nDOuXe4Z+68f7y8AjhAiVASyhsE
+0ntwlRjJW2FkoNyGr01djC4CbWZAhMy9GAm3J9Ste9W+9zvs2oTiX1e/r8UJ
+iSOppDkm2Ns7zTRmkHQQQ4oQM3dSE7Zq/ww/JGRr3cD0pHIvfSM8YQR7Vn9k
+c34KIRUCCNRl/0GGqszM2ydsTMaOY+Wt6EzG+cm4YpXpNP72mLAyVthU4aY/
+M/zMn/8VZ69rOPtZ4PmEhZpUfn3Yb/uDhmj32MnVgo7oNcVAhvrLdG6C4aDj
+n8tQFK6Hysi+35RacvVEqcBE8zT2YfFqeGXTx4S7Ylxj5UYaiO8+w3liYqHI
+mIjMGNYZ09MGzDIL6xsRFrAZfTJhjmvzLdhVLFSRqSKLHFMsVrGE5RZIYwWr
+XNdIa+Q1kp+Mv2/DIQIAAA==
+"""
+ )
+
+internal val NAVIGATION_STUBS =
+ arrayOf(NAV_CONTROLLER, NAV_DESTINATION, NAV_GRAPH, NAV_HOST, TEST_NAV_HOST)
+
+internal val TEST_CODE =
+ kotlinAndBytecodeStub(
+ "Test.kt",
+ "androidx/test",
+ 0xef517b0b,
+ """
+package androidx.test
+
val classInstanceRef = TestClass()
val classInstanceWithArgRef = TestClassWithArg(15)
@@ -66,4 +302,477 @@
abstract class TestAbstractComp { companion object }
class AbstractChildClassComp(val arg: Boolean): TestAbstractComp() { companion object }
object AbstractChildObjectComp: TestAbstractComp()
+""",
+ """
+META-INF/main.kotlin_module:
+H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgMuQSTsxLKcrPTKnQy0ssy0xPLMnM
+zxPi90ssc87PKynKz8lJLfIuEeIECnjkF5d4l3CJc/HCtZSkFpcIsYWkgiSU
+GLQYABRWGrdkAAAA
+""",
+ """
+androidx/test/AbstractChildClass.class:
+H4sIAAAAAAAA/4VQTWsTURQ9781XMk3MJH6labW1KrRZmLS4U4ppQAhMu6gl
+i2T1khnaRyYzMO9Fusxvce1GUAQXElz6o8T7JlUQFBfv3HvuO5z78f3Hl68A
+nuMpw65IozyT0XVHx0p3ehOlczHV/SuZRP1EKOWBMbT+VF0Q/FJ6sBjclzKV
++pjB3h8dDBms/YNhBQ48HzZKxEV+ycBGFfjYKIOjQlJ9JRXDXvi/CV6Q/2Ws
+e8aCjEcM9XCW6USmndNYi0hoQRI+f2vRUsxA2QCo4Yzq19KwLmXRIcPJatnw
+eZMXb7X0ebDh85LVXC2PeJedVBtuwFu8a3175/LAPq//ZiVSt+ySE7jG6Yhh
+O/z3UWgeau+Z0rOZpl37WRQz1EKZxmeL+STOL8QkoUojzKYiGYpcGn5T9N9k
+i3wav5aGbJ4vUi3n8VAqSb+9NM200DJLFQ7pkHaxaMPclTJOuQOXcJfYMXFO
+0W9/Rrm99QnVD4XmEaHRADvYI7y3VuEWauZulBk3OjMCemuvjjknRaf9EdX3
+f7WprAU3NhyPC9zBE4qviiEd3B7DGuDOAHcH1PY+pWgOsInWGExhC9tjeAo1
+hQcKvsJDBVchUKj/BOsI9uW0AgAA
+""",
+ """
+androidx/test/AbstractChildClassComp$Companion.class:
+H4sIAAAAAAAA/5VSTW/TQBB9u07jxARIWz4SvgolSC0SdRJxK0IqQUiRUpCg
+yqUHtLG3dBN7jbybqMec+CH8g56QOKCoR34UYtYJcKWX2Zn35s143/rnr+8/
+ADzHE4Y9oeM8U/FZaKWx4cHI2FxEtneqkriXCGN6Wfq55YLQKtM+GEN9LGYi
+TIT+FL4bjWVkfXgM5RdKK/uSwdvZHdawhnKAEnyGkj1VhqE9uNyqfYbOzmCS
+2UTpcDxLQ6WtzLVIwtfyREwT28s0TZhGNssPRT6R+f7uMAB3Kzdb0T/yY1qw
+dNfLTWNY/yM4lFbEwgrCeDrzyDzmQtUFMLAJ4WfKVW3K4g5DazEPAt7gAa9T
+tphXLr54jcW8y9vslV/hF1/LvM5db5e5Ca3/8cbHXYbqX4MY/CNq3ptY8riX
+xZLh+kBp+XaajmR+JEYJIRuDLBLJUOTK1Suw1tda5sVcSS8TfMimeSTfKMc1
+30+1VakcKqOo+UDrzApL6ww6ZG7J3ZhO7h6YPnyLqtBZQOfa02+onBf0Q4rl
+AuziEcXasgFVBECdUXZlJX5GJ1+Ja+eFnU5wawkuBUV2FdeI87BNVVCI7uE+
+mnhcLHyAVvFLkwfUWz+G18d6Hxt9bOIGpbjZp5m3j8EMGmgSbxAY3DEo/wZN
+V6SgDwMAAA==
+""",
+ """
+androidx/test/AbstractChildClassComp.class:
+H4sIAAAAAAAA/41SW08TQRT+ZrfXZZFSEQt4QUEsVdlCfBJCgjWaJoUHJE2E
+p2k7wtDtrNmZEh75LT77QtSQaGKIj/4o45lthRiN4WHPmXP2O9+5/vj55RuA
+p1hhmOeqE0eycxwYoU2w0dIm5m1TO5BhpxZyrWtR710WjOHOn8gdEhfoBOMy
+ZNakkmadIVXeXWwyuOXFpo80sh5SyJHN430GtuvDw0geDnyCmgOpGRYaV6lk
+lXLsC7NhaYh8lyG31g6HSZeuwjBvBVcyUllcZ1guN7qRIYbg8KgXSGVErHgY
+vBBveT+kxhRx9Nsmijd53BXx6qCfGx4mMMmQvyBjqF6pgcv0qz5KmLJDmGaY
+a0TxfnAoTCvmUumAKxUZbgimg63IbPXDkFof/13rpjC8ww0nn9M7cmmZzIq8
+FaABd8l/LK1VpVdnmeHV+UnRc0pO8p2feE5hxHNyqdL5yWx2xamyZyz7fLSY
+KTjTTtX9/j7jFFLb4xdWjkKmU7l0IWPp6GxmG/+/BiqM6sha91LXMMxs95WR
+PVFXR1LLVig2LhukE6hFHcEw1pBKbPV7LRHvcMIwFBtRm4dNHktrD51+XSkR
+JxMVFOy9jvpxW7yU9t/UME/zryxYpkmnaCIOpuzgqbxHZGVI3yJdtBdJ2iU7
+nXgfk7VOaIe0VzlDvjLzGaOnCcOTYSSwgiWSkwMUrmHMboBelo0WhgJ9A67A
+LoZ0uvIJox/+SeMPAEOaHBWVHQaXkKwW/ldMvGFnuPkRM6eJxyVim5DRRTpJ
+Y9WEu0INAzXy3ybGO3tw67hbx2wd93CfnpirYx4P9sA0FvBwDzmNMY2yhqex
+qJHRKGiMa5R+ATUXZWovBAAA
+""",
+ """
+androidx/test/AbstractChildObject.class:
+H4sIAAAAAAAA/4VSXWvUQBQ9M7ubZNPV1vrR3bZ+1PqgPpi2+GYR1kUhECPY
+ZaH0aZIMdrrZDCSzpY/75A/xHxQfCgqy6Js/SrwTV0VETMi995w5c27mJl+/
+ffgE4DHuMWyJIiu1ys4CIysT9JPKlCI1g2OVZ6+SE5kaF4xh/U/ZkMJPqYsG
+g7OvCmWeMjTuPxh10ILjowmXoWmOVcWwHf23zxMGbz/Nax8f3G72wvhg2I8H
+zzu4BL9N5GVrpcs3wYk0SSlUUQWiKLQRRmmqY23iaZ6T1ZVorA2ZBS+lEZkw
+gjg+OW3QuZkNbRvAwMbEnymLdqjKdqnBfOb7vMvrZz7zvrzl3flsj++wZ67H
+P79z+Aq30j2Gzejfc6GGrsWPxoZh4/W0MGoiw+JUVSrJZf/3W9OQBjqTDMuR
+KmQ8nSSyHArSMKxGOhX5SJTK4gXpH+hpmcoXyoLewnj0ly12aV7N+pA9Oz7K
+twg5lFcoc7pbNbpNKLCjoNx6eAHvvF6+sxCDqi2KnR8CtMkK8LD0a/Maqe21
+9BH88AKd91g+rwmOu3W8ie36f6PPQgarR2iEuBriWojruEEl1kJ00TsCq7CO
+DVqv4FfYrOB8Bw71GM6sAgAA
+""",
+ """
+androidx/test/AbstractChildObjectComp.class:
+H4sIAAAAAAAA/41SXWvUQBQ9M7ubzaarrfWju1Zrv6TVB9NW3yzCuigEYgS7
+LEifJpuhnW42I8ls6eM++UP8B8WHgoIs+uaPEu/EpSKCmJB77zlzciY5yfcf
+n74AeIJNhvsiS3KtkjPfyML4nbgwuRiY7rFKk9fxiaRRj97VwRhW/pT2qFzK
+S02FwdlXmTLPGCrbD/pN1OB4qKLOUDXHqmDYCv9rv6cM7v4gLb08cGvgBtFB
+rxN1XzRxBV6DyKsMG6HOj/wTaeJcqKzwRZZpI4zSNEfaROM0Jatr4VAbMvNf
+SSMSYQRxfHRaoQyYLQ1bwMCGxJ8pi3ZoSnZpg+nE83iLl9d04n57z1vTyR7f
+Yc/rLv/6weEL3Er3GFbDf+dDm9Yt92hoGJbfjDOjRjLITlWh4lR2fj85hdXV
+iWSYD1Umo/EolnlPkIZhMdQDkfZFriyekd6BHucD+VJZ0J4Z9/+yxS5lVi1f
+tG0jpL5CyKG+QJ3TWSvRPUK+jYN67eEF3PNyeXUmBh5jjWrzlwANsgJczF3e
+vERqe8x9Bn97geZHzJ+XBMd6We9io/z/6NOQweIhKgGuB7gR4CZu0YilAC20
+D8EK3MYyrRfwCtwp4PwEJ9evBbwCAAA=
+""",
+ """
+androidx/test/InterfaceChildClass.class:
+H4sIAAAAAAAA/4VQz28SQRT+ZhZY2FJZqFYKVq21teXg0sabprElMSFBTWrD
+AU4DO9Ipy26yMzQ98rd49mKiMfFgiEf/KOMbSpqYGD3M9973fnxv3vv569t3
+AM/whGFLxGGaqPAqMFKboB0bmb4XQ9k6V1HYioTWLhiDfyEuRRCJeBS8HVzI
+oXHhMNT/bD4juBFwkWXIvVCxMkcMmb3efpfB2dvvFuGi4CEDj7hIRwysV0QR
+qwVw3KJSc640w3bnvz97TgNG0hxbDVLuMZQ748REKg5eSyNCYQSV8MmlQ9sy
+CwULoIljil8py5rkhQcMJ/NZxeNVvnjzmcf9FY/nnep8dsib7GS1kvN5jTed
+Hx9y3M+clm9YnqprmXzWz1mlQ4bNzj/OQh+i+a6NPR0b2raVhJKh1FGxfDOd
+DGR6JgYRRSqdZCiirkiV5cug9y6ZpkP5SlmycTqNjZrIrtKKssdxnBhhVBJr
+HNApMzQnR69ib0uLcjsXecJHxI6Ic7Je4ytWGvUvKH1a1GwT2i6gjseE69dV
+8FG2pyPPqtGlSXdtqRXYi5LNNj6j9PGvMsXrgqUMx84Ct7BL9iXlblPuTh9O
+G+tt3G2jig1yUWtT/70+mMYm7vfhapQ1HmgUNR5q5DUqGmu/ARpxRv/QAgAA
+""",
+ """
+androidx/test/InterfaceChildClassComp$Companion.class:
+H4sIAAAAAAAA/5VSTW/TQBB9u07jxARIWz4SvqGp1CJRNxW3IiQIQrKUggRV
+Lj2gjb1tN7HXyLuJesyJH8I/6AmJA4p65EchZp0AV3qZnXlv3sz6rX/++v4D
+wHNsMoRCJ0WukrPQSmPDSFtZHItY9k5VmvRSYUwvzz53XBBa5doHY2iOxFSE
+qdAn4fvhSMbWh8dQfaG0si8ZvK3tQQMrqAaowGeo2FNlGLr9S+7aJ81Wf5zb
+VOlwNM1C5RRapOEbeSwmqe3l2thiEtu8OBDFWBb724MA3O1c78T/yE9ZyTLs
+XG4aw+ofwYG0IhFWEMazqUf2MRfqLoCBjQk/U67apSzpMnTmsyDgLR7wJmXz
+We3ii9eaz/b4Lnvt1/jF1ypvcte7x9yEzf8yx8ddhvpfhxj8Q+reGVtyuZcn
+kuF6X2n5bpINZXEohikha/08FulAFMrVS7ARaS2Lcq6ktwk+5pMilm+V49of
+JtqqTA6UUdT8SuvcCkvrDLrkbsV9Mp3cPTHd/CFVofOAzpWn31A7L+lHFKsl
+GOIxxcaiAXUEQJNRdmUpfkYnX4ob56WfTnBrAS4EZXYV14jz8ISqoBTdw320
+sVEufIBO+VeTB9TbPIIXYTXCWoR13KAUNyOaefsIzKCFNvEGgcEdg+pvYvaF
+YBIDAAA=
+""",
+ """
+androidx/test/InterfaceChildClassComp.class:
+H4sIAAAAAAAA/41S308TQRD+9lp67XFIWxRLEUUBKVW5gjwJIcEaTZOCCZIm
+wtO2Xcq11z1zu2145G/x2ReihkQTQ3z0jzLOlgoxGsPDzTczN/PNr/3x88s3
+AGtYY1jgshmFfvPY00JpryK1iA55Q5SP/KBZDrhS5bD7zgZjSLd5n3sBly3v
+db0tGtpGjGH6T4I9EpckNkYYEhu+9PUmQ7ywv1RjiBWWai5spBzE4ZDNoxYD
+23fhYiwFCzcoVB/5imGxeq3u1qlIS+gtw0Ps+wzJjUYwrOpdi2LeCC79UNq4
+xbBSqHZCTRReu9/1fJMjeeC9EIe8F+hyKJWOeg0dRts86oho/WKi2w4mkWNI
+XZIR0/VGuKq/7iKPabOHOwxz1TBqeW2h6xH3paJZZKi5pjDl7YR6pxcENHzm
+d7PbQvMm15x8VrcfoxszI1JGgHbcIf+xb6wSac0VhlfnJ1nHylmD7/zEsdKj
+jpWM585PZu1Vq8SeMfv5WDaRtvJWKfb9fcJKx3czl1aSUvLx5Eg6YehWGWaq
+/3kN1BU1YRvfckfTy9ntSe13RUX2feXXA7F1NR09gXLYFAzjVV+KnV63LqI9
+TjEM2WrY4EGNR76xh063IqWIBusUlOy8CXtRQ7z0zb+pYZ3aX1WwQmuOU1MJ
+wimzd9Kf0HoShHcJs+ZNEsZM40iSXCZrk6ItQqd4htHi9GeMn5JlwRtmgrQS
+ycmLKKSRMQcgzbDRvYh3YsjlmbsQjhQ/YfzDP2nci4AhTRI3kRom5zC4LNyv
+mHzLzjD1ETOnA0+MRjMF2aCJPA23OuB+jKeEZfLfI8bZA8QquF/BgwrmME8q
+Fip4iMUDMIUClg6QVMgoFBVchUfKmFmFCYX8L78zjKNFBAAA
+""",
+ """
+androidx/test/InterfaceChildObject.class:
+H4sIAAAAAAAA/4VSy27TQBQ9M0ljxzU0La+EUh59IGCB24odFVKJQLJkgkSj
+SKiriT1tJ3HGkj2JusyKD+EPKhaVQEIR7PgoxB0TihAS2Jr7OHPvuZ4z/vb9
+42cAT3CfYV3oJM9UchoYWZgg1EbmRyKW7ROVJq/7AxkbB4yhMRATEaRCHwe/
+0ArD6p/dXTIXDA4WGGp7SivzjKHy4GHPhwPXQxV1hqo5UQXDZvT/+U8Z3L04
+LYk8cNvthp2D7n6n/cLHEvw6gQ2GjSjLj4OBNP1cKF0EQuvMCKMyijuZ6YzT
+lKiWo2FmiCx4JY1IhBGE8dGkQoIwa+rWgIENCT9VNtumKNmhAbOp5/EmL9ds
+6n59x5uz6S7fZs8dl395X+MNbkt3GdaifyhDEx0LPB4akvDNWBs1kqGeqEL1
+U7n/+7NJpnaWSIalSGnZGY/6Mu8KqmFYibJYpD2RK5vPQe8gG+exfKls0poT
+9/6ixQ4JVqUz1mi1rILk79JBbb5CntNLV0XZPcoCqwb5hUfn8M7K7fV5MXAL
+G2T9nwVYpAjUeOmi+QZV22fxE/jbc1z+gOWzEuDYLO0dbJX/IsMVIrh6iEqI
+ayGuh9TapBCtEDexeghW0LA12i/gF7hdwP0BVe1yPcgCAAA=
+""",
+ """
+androidx/test/Outer$InnerClass.class:
+H4sIAAAAAAAA/4VU308cVRT+7sz+mB0WmOVXKay0yorL0nYAW62FVgFFBpel
+QkOs+HLZvcLAMoMzs6S+GJ76JzTRFxNjfOKhTRSMTQy2b/5NxnjuznS3LgSS
+mXvOPXPOd757zrnz979//AngJlYZhrhT8Vy78sgMhB+Yy7VAeDnLcYQ3V+W+
+nwRjMLb5Pjer3Nk0lze2RTlIQmVITNuOHdxjiOWt0TUGNT+6lkYcSR0xaAya
+LVFmvE0GZqWhoy0FBWnyD7Zsn+Fq8fzUUwxtmyKwGiiUwGLQy+7unusIJ5gg
+qLK79y3DMDG4GG246Hqb5rYINjxuO77JHccNeGC7pJfcoFSrVqfkARI68exj
+SEvwXEV8zWvVgGEtf1EKyyq2VmrqQl5pdKNHZhygkgXuauDZDh22Jz/6Glho
+pTNcarXN1uxqRXhJDOm4Isve08TOv+rAXQ1vUsP43p5wKgzX86ehT2eLkIng
+MHIS/G2GrCz0eY7vSMe8dJw737EgHcfSyOINqV2nw29xf2vOrQiGTDPScgKx
+Kc83Hg4aTZKJSR0TeJdOJL6p8SrNUm/+jMp/yZA7r+XUb75RFVTVuBtsCY+h
+6zQKkSnuuEHVdswlEfAKDzjZlN19lW4Qk0tKLqAh3yH7I1vuiKtSofH8+eRg
+SFf6FV0xTg50ehRD0xUtQbKNpEqyQ3vxWOs/OZhUxtlse1fCUAaUcfXFTwnF
+iC2mjKTcLbx8rC52Gxrp5KhpSuhEZkbmFOn6pGa0DcT62ThbePlEpcB06PGE
+kd5OeofUVzINeI3oDMS0uJGQXCeZPEH3GaOaxDzdueZMMSQf0McbO3QjYmG3
+Oou2I0q13Q3hPZAFlXV0y7y6xj1b7iPj4ErNCexdYTn7tm+TaabZDIb21YCX
+d5b4XuSda/W+zz2+K4jR/8LSTWaCtvqqW/PKYt6WEJcjiLVT6Wh6FPpJyTN3
+yR8TaRrpdPVpXaTdPH1XSOqFY6QKg7+h/RntFHxGawdki3spvg8pkkXa9YXe
+9K1TDgNpEpVmBwa9IaYpZ4RkvPAr2g8bcIm6sa8Okw4dIpgMkXsVPNwazM4M
+oF8JwcqACWIpOaWeQ3k4eIxLTxtBIdlUg2wqIrsUsekFjBT6cTnKPRIVK5ON
+ffc9NMlgujB4hMEQskSrCiYR6DJH6e+QlNSyz3Hl4TGudr11hBEZeYRRY/QI
+145w42nLMbIRo9d40Go2ajAS1aDO4HfcbC2DFsUz3MJ7EY+vSMp25QpjvyAe
+Oxz7C8oPiKuHYydQliTQNXp/lJZY2JNSvX1qUvsHmSTtmxXLNSqWw218QHmW
+SU9KUu/Xa3C/Hkr3CZ9igcr3eR3QwgrJL8h+hzo1tQ7VwrSFuxbu4UNS8ZGF
+Gcyug/mYw8fr6PTl84kPvb4mfBg+Mj66fHT7uFU33vZh+siS/h/DktczzQcA
+AA==
+""",
+ """
+androidx/test/Outer$InnerObject.class:
+H4sIAAAAAAAA/4VUS08TURT+7p0+ptMChSIUUBCpykNpQV1BTJRoHCzFCMEo
+q9t2hKHtjM7cEpas/AkuXLpwxULigkQTg7Dzf/g3jOdOR4vgI2nP+c655zXf
+uTNfv3/8DOAmbjGMCKfquXZ1Jy8tX+aXm9LycqbjWN5yecuqyDgYQ3pLbIt8
+XTgb+Z9ejSE2bzu2vM2gjU+spRBFzEAEcYaI3LR9htHif2rPMejSXZGe7Www
+9I5PFNt9Wl6KGCu63kZ+y5JlT9iOnxeO40ohbZdwyZWlZr1OUckTZXV0UuFN
+4W8uuFUrGM/Uto6+PaSRrZdNUafZzo0XTz/T3MQzhty/ulErUa5b1C7qyk3L
+Y+g5W4Vaz1fqATMGuKJDN0srq3dKC/dSGISRIOcQQ3ex5koKyy9ZUlSFFJTI
+G9sa7YUpkVACDKxG/h1bWQVC1RmG54e7wwbPcoOnD3cNriuQDLVuKFe6Uz9+
+ZWQPd2d5gd2N6/zobYyn+WImrQ3yQmRWT0cHI1lWYA+OX2uLiXSMvHHCjLBO
+OKGw6jbL1AyZP+wxjgmG+CrZ0zXJMPS46Ui7YZnOtu3bRNKdNnF0IVqL6Cra
+jlVqNsqWt6qIVPy5FVFfE56t7NDZsSJFpbYkXoR27nTtR8ITDYuG+K1JKrgC
+C3Xh+xaZxorb9CrWfVuVGAhLrJ0ZDjO0j0hA9YBaD+lrZMVId5CO0mk0sK6T
+lVcLUd7JA+j7BDimw2AgQ8dAqhWABJVSRZPk4UHyaJis9XS9D47a4VoYfrIz
+vXXoDvu2U3v2/pJKW0Jv2MkkzUn3T069QzSyN/UF/A2i2t7UIfiTyF4weIFk
+BDyuB8X6WglhMYX66M+IHagrTC8MAR3ZX1T0BwlA8hP40wMMfMD5/cChYZak
+4pFjEp3E6o2g3xR9b9RoDBeInuF1aCZGTFw06ekuEcSYiRwur4P5uIKr6zB8
+9Rv3EfORCUCfj3QAkiR/AL5kiZ3FBAAA
+""",
+ """
+androidx/test/Outer.class:
+H4sIAAAAAAAA/3VRwW7TQBB9u05ixzE0TSlNKE2BFmiKhNuKU6mQSgSSpZBK
+bRQJ5bRJVmUTx5bsTdRjTnwIf1BxqAQSiuDGRyHGbiAHileetzPz5s3u7M9f
+X74BeIFnDCsi6Eeh6l+4WsbaPRlrGZlgDMWBmAjXF8G5e9IdyJ42YTDkjlSg
+9CsGY6fWdpBFzkYGJkNGf1Axw2rjBr2XDNZRz08rbfCEbnnNs9Zxs/7GwS3Y
+eQreZthqhNG5O5C6GwkVxK4IglALrULaN0PdHPs+SS03hqEmMfed1KIvtKAY
+H00MuhFLTD4xYGBDil+oxNujXX+foTabOjYvc5sXZ1ObW4b14yMvz6YHfI8d
+ciPz2rT49085XuRJwQFLZGwvCGRU90VM1yukzvU8GKo33HV7QTexybD5X86f
+qT5kMFuUez4kyfXTcaDVSHrBRMWq68vjxQxoyPWwLxmWGiqQzfGoK6OWIA5D
+qRH2hN8WkUr8edBZHEVSsX0WjqOefKuSXGXep/1PF+zTY2TSCVaStyHcJi9H
+WCTktLKp95g8N5kzYXb3CtZlmn4yJwMlPCXrXBOQJynAQuFv8Rqxk6/wFfz9
+FZzPWLpMAwZ20nKOB/Rv0DkeEVYJa2mLLewSHpLMMgmXOjA8rHi442EVd2mL
+NQ9lVDpgMe5hvYNsDDvG/Ri5GBsxqr8BxLf8XAEDAAA=
+""",
+ """
+androidx/test/OuterComp$InnerClassComp$Companion.class:
+H4sIAAAAAAAA/5VTTW8SURQ9d4YyMGKlVC34/YGVGu0AcVdjohgTEmqT2rDp
+wjzgqQ+GN2bmTdMlK3+I/6ArExeGdOmPMt43oI0LE7u599x77rk3cx78+Pnt
+O4CnaBCaQo/iSI2OAyMTE+ylRsadaPqp3tWaUSiSJCttEFpF2gMRymNxJIJQ
+6A/B3mAsh8aDS8g/U1qZ5wS3sdUvYQV5Hzl4hJz5qBJCu3feYzuEVqM3iUyo
+dDA+mgZKs0SLMHgl34s0NJ1IJyZOhyaKd0U8kfHOVt+HY4+u14dn5LtpxhK2
+z7eNsPZbsCuNGAkjuOdMj1w2kGwo2gACTbh/rGzVZDRqEerzme87Vcd3yozm
+s8LpZ7c6n7WdJr30Cs7pl7xTduxsm+yGzf9zx8N1wsY/Zj3cJKz+LSAU/xhK
+8A5YsD0x/CqdaCQJl3pKyzfpdCDjAzEIuVPpRUMR9kWsbL1sls6WSn5L/22U
+xkP5Wlmutp9qo6ayrxLFwy+0jowwfC5Bix8jZx3i7NifBH/oPa4CaxnnlUdf
+UTjJ6Psc81nzMeocS4sBFOEDZWJ0YSl+wtlZiksnmf1WcHXRXAgydBGrzLl4
+wFWF2Ru4hduoZegO583s8F08zP4O7AVryodwu1jrotLFOi4zxJUu7944BCWo
+osZ8Aj/BtQT5X3gloppLAwAA
+""",
+ """
+androidx/test/OuterComp$InnerClassComp.class:
+H4sIAAAAAAAA/41UXVMURxQ9Pfs1Oywwi18IJJq4McuuukA0MYqJijEMATRi
+iGjy0OyOMLDMkJlZyrykfPInWJW8pCoPeeJBKwmkYlWK6Ft+UyqV0zPjomAs
+qqD73ru3zz197u35+98//gRwGl8LHJduw/ecxr1aaAdh7VortP0xb2W1ZLku
+raYMAuXmIATMJbkma03pLtSuzS/Z9TCHlEB21HGd8COBdNkanBVIlQdnC8gg
+ZyANXUB3FNIlf0FAWAUY6MhDQ4H54aITCJQn90bhvEDHgh1abTQWsgSMOn/z
+XNsNhwlZ91a/FaiSyd5Rj016/kJtyQ7nfem4QU26rhfK0PFoT3vhdKvZPK8u
+lDXI+6BAQRUpNey7stUMBe7u+QKWNblTwfN75lnAPuxXDPooaejNhL7jUoT9
+5cEXQOMo73RoZ+xyy2k2bD+HNw0cUW3pfRm//LxLF3S8xabK1VXbbQicLO+G
+310xQSfJYyipAu8IDKgmvC7xXZVYVoljr0+sqMRqAQN4Q1knKcCiDBbHvIYt
+UNw+abmhvaDuOBQPI6ethhEDw3iPN7K/ackm5+1A+RVduC1Qet0YcAbkfNOm
+shkvXLR9gZ7dKOQ1Wm8mr2Fob30tqUW6rJIDAYbLk8teSIza0tpKzeGFfFc2
+a1fiYRsjl9Bv1UPPn5L+MuWJn9oFA6NgzXwbTGBkj4O1TYBaX8Ql9TgvU9bn
+PKbsUDZkKElOW1lL8bsh1JJXC/iklxm/5yiPqmsNPsL1rftHDa1XMzRz677B
+P83UDU3Pcu/gnuLexbD+9IHey9TuEW1InBPdlzt7sqbWpw2lnv6U1cz0RN7M
+KW/82YPUxD5Tp711f0TXtTiJYcFwnrYxopsdfeleMSTGnz1M8WAhzngoaHfS
+7lL2jWIbXmf9vrSeMbOK84hQNzn0P3rlcF2g62XRBHI3mXRqme+//0bLDZ0V
+23LXnMDhkFzaHhzOYTyl3ZOOa0+3VuZt/6YaJDU/Xl02Z6XvKD8Jds6Esr48
+JVcTv7QT+7r05YpNZi8VKWyzs+kaM17Lr9tXHQVxOIGY3UWO70LjJxpcD6vO
+U4Ob9LLcD3DvUZ9q1Wn6mSj6Bb2rzNa4G5VN5Cv9v6HzcYQwy7ULagwqxKzy
+VAVf0jsYZ/O3bjUwtBQq5wsm/2PMmpoj7pnKr+hcb8Nlo2A1ginECQlMkeSe
+Hz6287B45QF+PAmrDgyTpeKUfwJtrn8Thx61D8Vk822y+YTsC7KYefRSrrj2
+8UTA4kD6u++hKwajlf4N9MeQt7imIBQCP11J+XPcFbWBJzgyt4mjPW9v4Lg6
+uYFBc3ADJzZw6tGOawwkjF5sj6BsxTaPWIOIwe84vVMGPTkvcAbvJzy+4q7a
+VapUf0YmvV79C9oPyKTWq1vQphTQCf7/qCLpuCe3ovalcvo/KObobytWaitW
+wll8yDpztHOK1AdR+XPIJVR7o6Ik9gSjc2ITH/+CscdRJIXb0dSp+focNyjy
+KK2L3O9E5WdIGbQFrrCvn9xBysJVC59aGIdFExMWPsMkEwJMYfoOzADdAa4F
+MKI1G6hIMUBPgH0BzkTBswFqAQYi++J/Kz3doBgJAAA=
+""",
+ """
+androidx/test/OuterComp$InnerObject.class:
+H4sIAAAAAAAA/41US08TURT+7p0+ptMC5SEUUHxQtFClBXVhICZIfAwpxQjB
+KKvbdoSh7QzO3BKWrPQfuHDpwhULiQsSTQzKzp/kwnjuMApCMCbtOd8597zm
+O3fm+89PXwDcwm2GYeHUPNeubRWk5cvCQkta3qzb3MiajmN5C5V1qyrjYAzp
+dbEpCg3hrBZ+ezWG2LTt2PIug5YbXU4hipiBCOIMEblm+wwjpf+oP8WgS3dR
+erazytCTGy0d9Tr0UsRwyfVWC+uWrHjCdvyCcBxXCmm7hMuuLLcaDYpKHiur
+o50Krwl/bdatWcGIpnbn4esfNLb1siUaNN+5XOnkc02NPmfI/qsbtRKVhkXt
+oq5cszyGrtNVqPV0tRGwY4ArSnSzvLg0U569n8IAjAQ5Bxk6S3VXUlhh3pKi
+JqSgRN7c1Gg/TImEEmBgdfJv2coqEqpNMLzY3x4yeIYbPL2/bXBdgWSodUO5
+0u36wSsjs789yYvsXlzn397FeJrPdae1AV6MTOrp6EAkw4rs0cEbbS6RjpE3
+TpgR1gknFFbdJpmaoe+MXcYxyhBfIt94XTIMPmk50m5aprNp+zYRNXNEHl2M
+w2V0lGzHKreaFctbUmQqDt2qaCwLz1Z26GxblKJanxcboZ09Wfux8ETTokH+
+apIKrsFsQ/i+Raax6La8qvXAViX6wxLLp4bDBO0kEtDdr1ZE+jpZMdJtpKN0
+Gg2sG2QV1FKUd2wP+i4BjvEwGMjRMZA6DECCSqmiSfLwIPlymKx1dXwIjo7C
+tTD8eGd6+9AZ9j1K7do5I5WhGz1hJ5M0J903ln+PaGQn/xX8LaLaTn4f/Glk
+Jxi8SDICHteDYr2HCWExhXrpz4gdqGtMLw0BHZk/VPQFCUDyM/izPfR/xPnd
+wKFhkqTikWMM7cTqzaBfnr49ajSGC0TP0Ao0ExdNXDLp6a4QxLCJLEZWwHxc
+xbUVGL765XzEfHQHoNdHOgBJkr8Ar2VkytEEAAA=
+""",
+ """
+androidx/test/OuterComp.class:
+H4sIAAAAAAAA/31RW2sTQRT+ZjbJbjaxTeMlibX10lqbCm5bfKpFqEVhIaZg
+Q0DyNEmGOslmV3YnoY958of4D4oPBQUJ+uaPEs9so0Wk7rDnO9fvzDnz4+fn
+rwCe4jFDRYT9OFL9U0/LRHtHYy3jw2j03gZjKA3ERHiBCE+8o+5A9rQNiyG3
+r0KlnzNYm/V2EVnkXGRgM2T0O5Uw1BpXcD5jcPZ7QVrtgpsSx28etw6ahy+L
+uAY3T84FhrVGFJ94A6m7sVBh4okwjLTQKiK9GenmOAiIaqkxjDSRea+lFn2h
+Bfn4aGLRZMyIvBFgYEPynypjbZPW32Goz6ZFl1e5y0uzqcsdy/n+gVdn012+
+zfa4lXlhO/zbxxwvcVOwywzNgh+GNEYgksTMwlBIHRd7Ydi4Yub1v8ts3KX5
+/pv7e9P3GewWxZ8MiX75zTjUaiT9cKIS1Q3kweVOaPGHUV8yLDZUKJvjUVfG
+LUE5DOVG1BNBW8TK2HNn8fJKkord42gc9+QrZWK1eZ/2P12wQ4+TSTdaM29F
+uE5WjrBEyOlkU+shWZ7ZO2F26xzOWRremCcDj+gAxYsE5IkKcFD4U1yhbPMV
+voC/PUfxExbPUoeFTZJlCt+jf4Xu8YBwlbCetljDFuEe0SwRcbkDy8d1Hzd8
+3MQtUlHxUUWtA5bgNpY7yCZwE9xJkEuwkmD1F4DlAzIZAwAA
+""",
+ """
+androidx/test/TestAbstract.class:
+H4sIAAAAAAAA/3VRy04CMRQ9t8AgIwriC/AR3Rh14ahxpzFBExMS1EQNG1eF
+mWgFOsm0EJd8i3/gysSFIS79KOPt6NbNyXnctqft1/f7B4AjrBHqUodJrMLn
+wEbGBncMjY6xiezaPIhQfpIjGfSlfgiuO0+RczME70RpZU8Jme2ddhE5eD6y
+yBOy9lEZwmrr/22PCXOtXmz7SgeXkZWhtJI9MRhluBQ5KDgAgXrsPyun9pmF
+B4SNydj3RVX4osxsMp7aqk7Gh2KfznKfL54oCzd3SG513p2617Pc6jwOI0Kp
+pXR0NRx0ouROdvrsVFpxV/bbMlFO/5n+bTxMutGFcqJ2M9RWDaK2MorThtax
+lVbF2mQ3IfjSf03dGzBWWQWpBnK7b5h6ZSJQY/RScx11xuLvAArw03wlxWWs
+pt9CmOaseI9MEzNNzDZRQpkp5pqoYP4eZLCARc4NfIMlA+8H7YuiztMBAAA=
+""",
+ """
+androidx/test/TestAbstractComp$Companion.class:
+H4sIAAAAAAAA/5VSTW/TQBB9u07jxARIWz4SPspXkNJK1E3FrQipBCFZSkGC
+Kpce0MZZYBN7jbzrqMec+CH8g56QOKCoR34UYtYJcENwmZ15b96M962///j6
+DcBjPGToCj3OMzU+Da00NjymcDgyNhex7Wfpx44LQqtM+2AMzYmYiTAR+n34
+ajSRsfXhMVSfKK3sUwavuz1sYA3VABX4DBX7QRmGncG/Ljlg6HUH08wmSoeT
+WRoqbWWuRRI+l+9EkVC7Jl0R2yw/EvlU5gfbwwDcLdvsxH/It2nJMuz+3zSG
+9V+CI2nFWFhBGE9nHhnGXKi7AAY2JfxUuWqPsnGPobOYBwFv8YA3KVvMa+ef
+vNZivs/32DO/xs8/V3mTu9595iZs/d0VHzcZ6r+tYfBdx+7Ukq/9bCwZLg+U
+li+LdCTzYzFKCNkYZLFIhiJXrl6BjUhrmfcTYYyk1wjeZEUeyxfKce3XhbYq
+lUNlFDUfap1ZYWmdQY9srbi70sndo9In36EqdJenc23nC2pnJX2XYrUEe7hH
+sbFsQB0B0GSUXViJH9HJV+LGWWmkE1xbgktBmV3EJeI83KcqKEW3cBttPCgX
+bqFT/sDkAfU2T+BFWI+wEWETVyjF1YhmXj8BM2ihTbxBYHDDoPoTIO6Wpv0C
+AAA=
+""",
+ """
+androidx/test/TestAbstractComp.class:
+H4sIAAAAAAAA/4VRXWsTQRQ9s5vPdWOT+pVYramtMc2D2xRBsEWoEWEhTUFL
+QPI0ScY6yWZWdiahj/kt/oPiQ0FBgo/+KPHuNrYPQn25Z+6dc889c+fX728/
+ADxHg2Gdq2EUyuGpZ4Q23jGFg742ER+YVjj5nAVjKI74jHsBVyfeUX8kBiYL
+myGzL5U0rxjs+nbXRRoZBylkGVLmk9QM1fb10nsMuf1BsBSpX0/eigNXMlRZ
+uAzNenscGur1RrOJJ5URkeKB90Z85NOAGhR1TgcmjA55NBbR3oXBmw4KWGHI
+X4oxNP7j8mrwnosSVvOwcIthsx1GJ95ImH7EpdIeVyo03BBNe53QdKZBQO8r
+/XV5KAwfcsOpZk1mNi2fxSEfBzCwMdVPZZzt0GnYZKgt5q5jlS3HKi7mjpWz
+crXyYl61d60d9pLZr9M/v2SsohWzd1mskY2dPxsbhrV3U2XkRPhqJrXsB+Lg
+yhz9TiscCoaVtlSiM530RXTMicOw2g4HPOjySMb5suj6SomoFXCtBTU778Np
+NBBvZXxXWc7p/jMltUFbSiVPq8RLI9ykLEN4h9AiTCfZFmVevADCdOMcubPk
++smSDDRRo+heEJCHQ5jDjcvmMpIVwv2Owgd2juJX3D5LKjaeUnSIVyDFEhmp
+J9qPsU34gup3SfFeD7aPso+Kj/tYoyMe+HiI9R6YxiNUe0hpOBobGhmN0h/c
+zA/4OwMAAA==
+""",
+ """
+androidx/test/TestClass.class:
+H4sIAAAAAAAA/3VRu04CQRQ9d5BFVpQFX+CrVgsXjZ3GRE1MSFATNTRWA7vR
+gWU2YQZCybf4B1YmFoZY+lHGO6utzcl53Jk5N/P1/f4B4BjbhHWpo2Gqoklo
+Y2PDB4bLRBpTABGCnhzLMJH6Kbzt9OKuLSBH8E6VVvaMkNvda5eQh+djDgXC
+nH1WhlBv/XPnCaHS6qc2UTq8jq2MpJXsicE4x3XIQdEBCNRnf6KcajCLDgk7
+s6nvi5rwRcBsNq3NpkeiQRf5zxdPBMJNHZE7W3APHvQtF7pMo5hQbikd34wG
+nXj4IDsJO9VW2pVJWw6V03+mf5+Oht34SjlRvxtpqwZxWxnF6bnWqZVWpdrg
+EIL3/evp1messQozDeT33zD/ykSgzuhl5hI2GEu/AyjCz/LNDNexlX0HYYGz
+0iNyTSw2sdREGQFTVJqoYvkRZLCCVc4NfIM1A+8HjoCWJ8sBAAA=
+""",
+ """
+androidx/test/TestClassComp$Companion.class:
+H4sIAAAAAAAA/5VSTW/TQBB9s07jxARIWz4SyjepaJGom4pbERIEIUVKQYIq
+lx7QJllgE3uNvJuox5z4IfyDnpA4oKhHfhRi1ilwQ3CZnXlv3oz3rb//+PoN
+wCNsEjalGeWZHh3HTlkXH3LoJNLaTpZ+bPkgjc5MCCLUx3Im40Sa9/GrwVgN
+XYiAUH6sjXZPCMHWdr+GFZQjlBASSu6DtoT7vX/asE9ob/UmmUu0icezNNbG
+qdzIJH6u3slp4jqZsS6fDl2WH8h8ovL97X4E4Tett4Z/yLdpwRJ2/m8aYfWX
+4EA5OZJOMibSWcBWkQ9VH0CgCePH2le7nI3ahNZiHkWiISJR52wxr5x+ChqL
++Z7YpWdhRZx+Lou68L175Cds/MWSEBuE6m9fCKGndyaOHe1kI0W42NNGvZym
+A5UfykHCyFovG8qkL3Pt6zOw1jVG5cVcxe8Qvcmm+VC90J5rvp4ap1PV11Zz
+81NjMicdr7Nos6clf1E+hX9O/t5bXMX+5nyuPPiCyklB3+ZYLsB7uMOxtmxA
+FRFQJ87OnYkf8inOxLWTwkUvuLIEl4IiO48LzAW4y1VUiK7jBpq8wC+8iVbx
+37IH3Fs/QtDFahdrXazjEqe43OWZV49AFg00mbeILK5ZlH8CxwhM7PQCAAA=
+""",
+ """
+androidx/test/TestClassComp.class:
+H4sIAAAAAAAA/31RXWsTQRQ9s5vPdWOT+pVYq9WmNu2D2xRBMEXQiLCQtqAl
+IHmaJGOdZDMrO7Ohj/kt/oPiQ0FBgo/+KPHuNrYPQl7umXvn3HPP3Pn95/tP
+AM+xy7DG1TAK5fDMM0Ib74RCO+Bat8PJlzwYQ3nEp9wLuDr1jvsjMTB52Ay5
+A6mkecVgN3a6LrLIOcggz5Axn6VmWO8s0W0xFA4GwUJhawmzngSuZKjycBma
+jc44NNTojaYTTyojIsUD7634xOPAtEOlTRQPTBgd8mgsotaltZsOSlhhKF6J
+MWwv83c9teWigtUiLNxi2OyE0ak3EqYfcam0x5UKDTdE095RaI7iIKCXVf5Z
+PBSGD7nhVLMmU5sWzpJQTAIY2JjqZzLJ9ug0bDLU5zPXsaqWY5XnM8cqWNX5
+bMPet/bYS2a/yf76mrPKVsLdZ4lCPjH9bGzoE9/HysiJ8NVUatkPxOtra/Ql
+7XAoGFY6UomjeNIX0QknDsNqJxzwoMsjmeSLousrJaJ0F4KanQ9hHA3EO5nc
+1RZzuv9NQZN2lEkfVktWRrhJWY7wDqFFmE2zOmVe8nzC7O4FCufp9daCDGp7
+StG9JKAIh7CAG1fNVaQLhPsDpY/sAuVvuH2eVmxsU3SIVyLFChlppNpPsEP4
+gup3SfFeD7aPqo+aj/tYoyMe+FjHwx6YxiNs9JDRcDQea+Q0Kn8BDq7wpy0D
+AAA=
+""",
+ """
+androidx/test/TestClassWithArg.class:
+H4sIAAAAAAAA/31QTWsTURQ9781nxsRM4leaaq3aRZtFJy3ulGIMCANRoZZ0
+kdVLZkhfM5mBeS+ly/wW124ERXAhwaU/SrwvDa5EeJx7z32Hcz9+/f7+A8Bz
+7DHsiDwpC5lcRzpVOjoj6GdCqXOpL3rl1ANjCC/FlYgykU+j9+PLdKI9WAzu
+S5lLfcJg78cHQwZr/2BYhQMvgA2fuCinDCyuIsCtCjiqJNUXUjHsDv7f9QW5
+T1PdMwZkGzM0BrNCZzKP3qZaJEILkvD5lUVrMAMVA6B2M6pfS8O6lCVHDP3V
+shnwFg94uFoG9HjoB9y3WqvlMe+y17WmG/I271o/P7o8tE8bf5lP6rbtO6Fr
+rI6ZaeCZWQ9nmnbpF0nKUB/IPH23mI/T8kyMM6o0B8VEZENRSsM3xeBDsSgn
+6RtpyNbpItdyng6lkvTby/NCCy2LXOGIDmWvV2mau1HGKXfgEj4mdkKcUww6
+31DpbH9F7fNas0toNECIJ4T3b1S4jbq5DGXGjQ5J/42NV2QORtHpfEHt0z9t
+qjeCjQ3H0zXu4BnFV+shHdwZwYpxN8a9mNo+oBStGFtoj8AUtvFwBE+hrvBI
+IVijqxAqNP4AZEwrjogCAAA=
+""",
+ """
+androidx/test/TestClassWithArgComp$Companion.class:
+H4sIAAAAAAAA/5VSTW8TMRB99qbZZAmQtnwkfLdNpRZBt6m4FSGVIKRIKUhQ
+hUMPyElM62TXi2wn6jEnfgj/oCckDijqkR+FGG8CHFEv45n35s14n/fnr+8/
+ADzDJsMToQcmU4Oz2Enr4iMKrURY+0G50wNz0srSzw0fhFaZDsEYqkMxEXEi
+9En8tjeUfRciYCg+V1q5FwzB1na3giUUIxQQMhTcqbIMO53LLNpnaG51RplL
+lI6HkzRW2kmjRRK/kp/EOHGtTFtnxn2XmUNhRtLsb3cjcL9wtdH/R35Mc9bv
+v9Q0huU/gkPpxEA4QRhPJwEZx3wo+wAGNiL8TPlql7JBk6Exm0YRr/GIVymb
+TUsXX4LabLrHd9nLsMQvvhZ5lfvePeYnrP/fmRB3Gcp/7WEIfdfOyJG/rWwg
+Ga53lJZvxmlPmiPRSwhZ6WR9kXSFUb5egJW21tLk4yW9SvQ+G5u+fK08V383
+1k6lsqusouYDrTMnHK2zaJK1Bf+9dHL/uHTth1TF3gA6lx5/Q+k8px9RLObg
+JtYoVuYNKCMCqoyyKwvxUzr5Qlw5z830gltzcC7Is6u4RlyAdaqiXHQP91HH
+Rr7wARr5z0weUG/1GEEby22stLGKG5TiZptm3j4Gs6ihTrxFZHHHovgbuLwQ
+4QkDAAA=
+""",
+ """
+androidx/test/TestClassWithArgComp.class:
+H4sIAAAAAAAA/41SW08TQRT+ZnvZ7VpkqYgFvCAXLRXZQnwSQoI1xk1KTZDU
+GJ6m7Vi23c6a3WnDI7/FZ1+IGhJNDPHRH2U8s63woAkmu+fMOXPO953L/Pz1
+9TuAJ9hgWOSyHYV++9hVIlbuAYlqwOP4ja+OdqNONey/N8EYnC4fcjfgsuO+
+anZFS5lIMWS3femrHYZ0yVttMKRKq408MjBtpGGRzaMOA/PysHEtBwN5ClVH
+fsywXLuaeYsYOkLtahCC9his7VYwply7On9ZCy79UJq4wbBRqvVCRflud9h3
+falEJHngPhfv+CBQ1VDGKhq0VBjt8agnoq1RLzdtTGOGIXcBxrD+H8Vfkm/l
+UcSsbn+OYakWRh23K1Qz4r6MXS5lqLiisNith6o+CAJqe+pPpXtC8TZXnHxG
+f5iitTEtclqARtsj/7GvrQqd2rTRl+cnBdsoGrbhnJ/Y9BmOZRtWunh+smBu
+GhX2lJnPJgpZx5gzKqkfH7KGk96furAsSplLWxknq/E2mWYxdX/rPcUwvz+Q
+yu8LTw792G8GYveyfFptNWwLhsmaL0V90G+K6IBTDEOhFrZ40OCRr+2xM+9J
+KaJkbIKS7dfhIGqJF76+mx3zNP5iwQbNMU39GpjVY6XyymRlSd8mXdAvjXSK
+7EzifUTWDkUbpO3yGXLl+S+YOE0Q1saZwAoek5wZReE6JvV86aTRaB1w6B9h
+uXrspDPlz5j4+E+Y/ChgDGNRUeY4uYhkcch/w/RbdoZbnzB/mnhSWE8IGb02
+I2nMTbBXUSFdJf8dQrx7iJSHex4WPNzHIh2x5GEZK4dgMR7g4SGsGJMxSjHs
+RGZjODGmYhR/A+R3KVn3AwAA
+""",
+ """
+androidx/test/TestGraph.class:
+H4sIAAAAAAAA/3VSTW/TQBB9s/mw4waalo8klO/2UDjgtuJGhVQqQJaMkWgU
+qeppE6/aTRwb2Zuox5z4IfyDikMlkFAEN34UYtZEcEB4pTfzZt887Yz84+fn
+rwCeYovQlmmcZzo+940qjN9jeJ3L92cOiNAayZn0E5me+m8HIzU0DiqE+r5O
+tXlOqGw/6jdRQ91DFQ6has50QeiG//F8RnD3h0nZ7UHYFjeIjnoH0eHLJq7A
+a3DxKmEzzPJTf6TMIJc6LXyZppmRRmecR5mJpknCVmvhODNs5r9RRsbSSK6J
+yazCk5GFhgUQaMz1c23ZDmfxLmFrMfc80RGeaHG2mLvfP4jOYr4nduiF44pv
+H+uiJax2j6yDYyd4MjaEjXfT1OiJCtKZLvQgUQd/n8bzH2axIqyGOlXRdDJQ
+eU+yhrAeZkOZ9GWuLV8WvaNsmg/VK21Jd2nc/8cWu7yUajlJ1+6I411mdY4t
+joJPrWT3mPl2Xo61x5dwL8rr+0sxuPUBY/O3AA3mgIuVP81tVttv5QvE8SWa
+n7B6URYEHpZ4B5vlb8O7Z4P1E1QCXAtwPcAN3OQU7QAddE9ABW5hg+8LeAVu
+F6j/Ai/P7yRzAgAA
+""",
+ """
+androidx/test/TestInterface.class:
+H4sIAAAAAAAA/32Oz0rDQBDGv9lo08Z/qVqoiK9g2tKbJy9CoCKoeMlpm2xl
+m3QD2Wnpsc/lQXr2ocSJ3p2Bb76Zgd/M1/fHJ4ApBoRr7YqmtsU2YeM5eRVJ
+HZtmoXMTggjxUm90Umn3njzNlybnEAGhPytrrqxLHg3rQrO+I6jVJhAstdJr
+BQQqZb61bTcSV4wJg/2uG6mhilQsbjHc7yZqRO1yQriZ/fOP3BBk2M5uSyZE
+L/W6yc2DrQzh6nnt2K7Mm/V2Xpl752rWbGvnO8LGAf5C4eJXz3EpdSy8Q8lO
+hiBFmKKboodILI5SHOMkA3mc4iyD8og9+j/Vk+x/PAEAAA==
+""",
+ """
+androidx/test/TestKt.class:
+H4sIAAAAAAAA/4VUbU/TUBR+bjvWrgzW8b6BiLzo5gsFfBc0ISQmjRMSJBhC
+YtJt11kYbdJ7R/jIb/EXqHwgkcQQP/qjjOc2xGkLug/3nvvc53l6zunpfvz8
++g3AAzxnGPKCZhT6zSNHciGdLVpeSQOMwd7zDj2n7QUtZ6O+xxuE6gyDLS7X
+2p4QbiCkFzT4Jn/PMF6p1tJGMW+ZYaYWRi1nj8t65PmBcLwgCKUn/ZDi9VCu
+d9ptYtmNlG3pStM8TORy0GAxlJMpvfXlh9WoFVtMX53ZBY0ePdq4Sj71P3Ee
+/SioRGyGMUrEDQIepRuUTmOjI3k026VTGsP+5eJkEilpHkMYVkmMMJgrjbYf
++PIFg16pblNxV1RgoMyQXYm5eUygZGEc1xgm/12xgesMmYpb3VaiGxamMJ0S
+JTM0MGthThGLtf1QUoLOay69pic9qls7ONRpHplacmoBA9tXgUaXR76KFihq
+LjK8Oz8uW+fHljamWZqpJ3ZtumgTQVtg3z9mTeKVM6Zm64RmCOzpglnbINAk
+MNcFLbtXPWWJpvyScgzcZ7C6NTEYqjfz+5Lmf7MTSP+Au8GhL/x6m692R5y6
+tRY2OUOh5gd8vXNQ59GWRxyGfNeNE896E3aiBn/pq7vSheV2yhCL9KYz1BMd
+ZfUZULce0ylLu0F7WU1kCqMBSWAZlNBDJw1P6DRBqPplvqD3U/wGnl5wFfNP
+XQl59KVVxaQqm1ANYDCtGk2qzL9UJsZIyWLVGuLRwMwZxndOMXmC3jNM7diF
+U8ycoHiGuTi+eYLRz79N+2NRBhZZjpCdjmd0tuh2jv7/HpL5shozPMIK7RuE
+36KmVHahu6i6uO3iDu66uId5Fw4WdsFU+5d2kRcwBXICPQJZgX6BglBgn8CQ
+wLDAgMDgL1Y9gwBpBQAA
+""",
+ """
+androidx/test/TestObject.class:
+H4sIAAAAAAAA/3VSTWvbQBB9s7ZlWXEbN/2InfQrH4ekhyoJvTUUktCCQFWh
+MYaQ09pa0rVlCaS1ydGn/pD+g9BDoIVi2lt/VOmscNpDqRbezHs789gZ9PPX
+l28AXmCb0JZpnGc6vvSNKozfZXjXH6qBqYMIraGcSj+R6YV/o1YIzqFOtXlF
+qOzs9pqowfFQRZ1QNR90QVgL/2f6kuAeDpKy3YOwPW4QnXaPopPXTdyC12Dx
+NmErzPILf6hMP5c6LXyZppmRRmecR5mJJknCVnfCUWbYzH+rjIylkayJ8bTC
+s5GFhgUQaMT6pbZsj7N4n7A9n3meaAtPtDibz9wfH0V7PjsQe3Rcd8X3T45o
+CVt7QNahbkd4PjKE9feT1OixCtKpLnQ/UUd/n8YLOMliRVgOdaqiybiv8q7k
+GsJKmA1k0pO5tnwheqfZJB+oN9qSzsK4948t9nkp1XKSjt0Rx8fMHI4tjoJP
+rWRPmPl2Xo61Z9dwr8rrp4tioImNEssCNNgKcLH0p3mVq+239BXi7BrNz1i+
+KgWBzRIfYav8cXj3bLByjkqAuwHuBbiPB5xiNUAbnXNQgTWs830Br8DDAs5v
+jU1b0HUCAAA=
"""
+ )
+ .bytecode
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongNavigateRouteDetectorTest.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongNavigateRouteDetectorTest.kt
index 4af2ebc..ce9918d 100644
--- a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongNavigateRouteDetectorTest.kt
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongNavigateRouteDetectorTest.kt
@@ -17,28 +17,21 @@
package androidx.navigation.runtime.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest.compiled
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestMode
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import org.junit.runners.JUnit4
-@RunWith(Parameterized::class)
-class WrongNavigateRouteDetectorTest(private val testFile: TestFile) : LintDetectorTest() {
+@RunWith(JUnit4::class)
+class WrongNavigateRouteDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = WrongNavigateRouteDetector()
override fun getIssues(): MutableList<Issue> =
mutableListOf(WrongNavigateRouteDetector.WrongNavigateRouteType)
- private companion object {
- @JvmStatic @Parameterized.Parameters public fun data() = listOf(SOURCECODE, BYTECODE)
- }
-
@Test
fun testEmptyConstructorNoError() {
lint()
@@ -47,7 +40,8 @@
"""
package com.example
- import androidx.navigation.*
+ import androidx.navigation.NavController
+ import androidx.test.*
fun createGraph() {
val navController = NavController()
@@ -57,7 +51,8 @@
"""
)
.indented(),
- testFile,
+ *NAVIGATION_STUBS,
+ TEST_CODE
)
.skipTestModes(TestMode.FULLY_QUALIFIED)
.run()
@@ -72,7 +67,8 @@
"""
package com.example
- import androidx.navigation.*
+ import androidx.navigation.NavController
+ import androidx.test.*
fun createGraph() {
val navController = NavController()
@@ -98,7 +94,8 @@
"""
)
.indented(),
- testFile,
+ *NAVIGATION_STUBS,
+ TEST_CODE
)
.skipTestModes(TestMode.FULLY_QUALIFIED)
.run()
@@ -113,7 +110,8 @@
"""
package com.example
- import androidx.navigation.*
+ import androidx.navigation.NavController
+ import androidx.test.*
fun createGraph() {
val navController = NavController()
@@ -150,91 +148,92 @@
"""
)
.indented(),
- testFile,
+ *NAVIGATION_STUBS,
+ TEST_CODE
)
.skipTestModes(TestMode.FULLY_QUALIFIED)
.run()
.expect(
"""
-src/com/example/test.kt:7: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:8: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestClass)
~~~~~~~~~
-src/com/example/test.kt:8: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:9: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestClass::class)
~~~~~~~~~~~~~~~~
-src/com/example/test.kt:9: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:10: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestClassWithArg)
~~~~~~~~~~~~~~~~
-src/com/example/test.kt:10: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:11: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestClassWithArg::class)
~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:11: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:12: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestInterface)
~~~~~~~~~~~~~
-src/com/example/test.kt:12: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:13: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestInterface::class)
~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:13: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:14: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = InterfaceChildClass)
~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:14: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:15: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = InterfaceChildClass::class)
~~~~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:15: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:16: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestAbstract)
~~~~~~~~~~~~
-src/com/example/test.kt:16: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:17: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestAbstract::class)
~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:17: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:18: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = AbstractChildClass)
~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:18: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:19: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = AbstractChildClass::class)
~~~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:19: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:20: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = InterfaceChildClass::class)
~~~~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:20: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:21: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = Outer.InnerClass)
~~~~~~~~~~~~~~~~
-src/com/example/test.kt:21: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:22: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = Outer.InnerClass::class)
~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:24: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:25: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestClassComp)
~~~~~~~~~~~~~
-src/com/example/test.kt:25: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:26: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestClassComp::class)
~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:26: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:27: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestClassWithArgComp)
~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:27: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:28: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestClassWithArgComp::class)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:28: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:29: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = OuterComp.InnerClassComp)
~~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:29: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:30: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = OuterComp.InnerClassComp::class)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:30: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:31: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = InterfaceChildClassComp)
~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:31: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:32: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = InterfaceChildClassComp::class)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:32: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:33: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = AbstractChildClassComp)
~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:33: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:34: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = AbstractChildClassComp::class)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:34: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:35: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestAbstractComp)
~~~~~~~~~~~~~~~~
-src/com/example/test.kt:35: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
+src/com/example/test.kt:36: Error: The route should be a destination class instance or destination object. [WrongNavigateRouteType]
navController.navigate(route = TestAbstractComp::class)
~~~~~~~~~~~~~~~~~~~~~~~
27 errors, 0 warnings
@@ -242,528 +241,3 @@
)
}
}
-
-private val SOURCECODE =
- kotlin(
- """
-
-package androidx.navigation
-
-public open class NavController {
-
- public fun navigate(resId: Int) {}
-
- public fun navigate(route: String) {}
-
- public fun <T : Any> navigate(route: T) {}
-}
-""" +
- TEST_CLASS
- )
- .indented()
-
-private val BYTECODE =
- compiled(
- "libs/StartDestinationLint.jar",
- SOURCECODE,
- 0xb1569275,
- """
- META-INF/main.kotlin_module:
- H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgUuMSTsxLKcrPTKnQy0ssy0xPLMnM
- zxPi90ssc87PKynKz8lJLfIu4RLl4k7Oz9VLrUjMLchJFWILSS0u8S5RYtBi
- AADcysPxVwAAAA==
- """,
- """
- androidx/navigation/AbstractChildClass.class:
- H4sIAAAAAAAA/41QTW9SQRQ9M+8DeAV54Belamv9CGUhtHGnaaQkJiTYRW1Y
- wGrgvdAJj/eSNwPpkt/i2o2JxsSFIS79UcY7j2pcsHAxZ+65c3LunfPz17fv
- AF7iGcNzEQdpIoPrViyWciq0TOJWZ6x0Kia6eyWjoBsJpXJgDAfbtJeh0n/0
- OVgM7msZS33KYDeGRwMGq3E0KMJBzoONPHGRThnYsAgPOwVwFEmqr6RiaPT/
- b5tXNGUa6o4xIvshQ6U/S3Qk49a7UItAaEESPl9a9E1moGAANHZG/WtpWJuq
- 4JjhbL2qerzGs7Needzf8Xjeqq1XJ7zNzkpV1+d13rZ+fHC5b19U/rI8qet2
- 3vFd43TCcLh1/X8Doq1oCf9cLLtJrNMkisL0xUxTAN0kCBnKfRmH54v5OEwv
- xTiiTrWfTEQ0EKk0/KbpvU8W6SR8Kw3ZvVjEWs7DgVSSXjtxnOhsssIxpWtn
- /66asKniVDtwCQ+InRLndHvNryg0976g9CnTPCY0GqCBQ8J7GxVuoWxipMq4
- Uerw6Wy8WiZdup3mZ5Q+brUpbgQ3NhxPMtzHU7rfZEs6uD2C1cOdHu72aOx9
- KlHrYRf1EZjCHh6MkFMoKzxU8BQeKbgKvkLlN2AWm1jVAgAA
- """,
- """
- androidx/navigation/AbstractChildClassComp$Companion.class:
- H4sIAAAAAAAA/51Sy27TQBQ9Y6dxYgKkLY+E9yNIbSXqpqrYBCGVIKRIaZEo
- yqYLNLGHdhJ7Bo0nUZdZ8SH8QVdILFDUJR+FuOME2AKb+zr33Ds+199/fP0G
- YA9PGPa4SoyWyVmk+FSecCu1ivaHuTU8tt1TmSbdlOd5V2cfW85wRQ0BGEN9
- xKc8Srk6id4MRyK2AXyG8nOppH3B4G9sDmpYQTlECQFDyZ7KnOFZ/38Wdhja
- G/2xtqlU0WiaRVJZYRRPo1fiA5+ktqsVTZjEVpsDbsbCdDYHITy3eL0V/wHf
- ZwXKsP1v0xhWfxEOhOUJt5xqXjb1SUjmTNUZMLAx1c+ky3YoStoMrfksDL2G
- F3p1iuazysUnvzGf7Xo77GVQ8S4+l72653p3mZuw9fcKBbjNUP0tEx3lkE/p
- 9dboNBVme2xJ+K5OBMPVvlTicJINhXnHhylV1vo65umAG+nyZbHWU0qYYoGg
- c4VHemJi8Vo6rPl2oqzMxEDmkpr3ldK2eF2ONmldcgKQ99zV6TvuUxY5Rciv
- bH1B5byAH5AtF8UOHpKtLRpQRQjUGUWXluSn5L0luXZeqOsINxbFBaGILuMK
- YT4eURYWpDu4iyYeFwvvoVX87aQB9daP4few2sNaD+u4RiGu92jmzWOwHA00
- Cc8R5riVo/wTjWwvPCoDAAA=
- """,
- """
- androidx/navigation/AbstractChildClassComp.class:
- H4sIAAAAAAAA/5VSS08TURT+7vQ1HYqUiljABwpiqcgUQlxYQoI1mialCyRN
- hNVtey2XTu+YubcNS36LazdEDYkmhrj0RxnPTCskygIXc849Z77znefPX1+/
- A9jAOkORq3bgy/axq/hAdriRvnK3m9oEvGUqh9JrVzyudcXvvU+BMSxehd8T
- 2lzERMgYQ3JTKmm2GOKF/eUGQ6yw3MgggZSDOGyyedBhYPsZOBhLw0KGoOZQ
- aoaV2vWrKlOmjjDbIRml2GewN1veKPXG9XkWQ8EVAVK4ybBWqHV9Qzzu0aDn
- SmVEoLjnvhTveN+jJhVx9FvGD3Z40BVBedjbLQdTmGZIX5AxPPuPZi6LKGeQ
- x0w4llmGhZofdNwjYZoBl0q7XCnfRDzarfum3vc8GsPkn4p3hOFtbjj5rN4g
- RqtmoUiHAjTyLvmPZWiV6NVeY3h9fpJzrLwVfecnjpUdcyw7nj8/mU+tWyX2
- nKVejOeSWWvWKsV+fEha2fju5IVlU8hs3E5kkyEdHdXSlS3/fSVUHlWTrfMB
- jdMEvueJYLVrGOZ2+8rInqiqgdSy6Ynty37pRip+WzBM1KQS9X6vKYI9ThiG
- XM1vca/BAxnaI2emqpQIogELCnbe+P2gJV7J8N/MKE/jnyxYo8HHaUAWZsI9
- UJ1PyEqSvkM6F54s6RjZici7QtYWoS3STvEM6eLcF4yfRgxPR5FAGaskp4co
- 3MBEuBB6hWy0P2TpG3K54Z5IJ4qfMf7xSprMEDCisamo1Cg4j2jTyHzD1Ft2
- htufMHcaeWJEHCZkdKZW1Fgp4i5Sw0CF/HeJ8d4BYlXcr2K+igd4SE8sVLGI
- RwdgGkt4fABbY0KjoOFoLGskNbIakxr53/A96KpcBAAA
- """,
- """
- androidx/navigation/AbstractChildObject.class:
- H4sIAAAAAAAA/41Sy27TQBQ9M3k5bqChPJpQHqVFgrDAbcWOCilEIFkyRqJR
- JNTV2B610zgzkj2JusyKD+EPKhaVQEIR7PgoxLUJD4kusOV759w5c67uGX/7
- /vEzgCe4z/BA6CQzKjn1tJipI2GV0V4/ym0mYjs4VmnyOjqRsW2AMWxeRB7K
- 3P460ECFob6vtLLPGCoPe6MWaqi7qKLBULXHKmfoBf/Z8ymDsx+npZoLXkg4
- fngw7IeDFy1cgtuk4mWG7cBkR96JtFEmlM49obWxpWruhcaG0zQlqSvB2FgS
- 815JKxJhBdX4ZFYhJ1gRmkUAAxtT/VQVaIdWyS41WMxdl3d4+S3mztd3vLOY
- 7/Ed9rzh8C/v67zNC+oew9aFw/3tEbVth2I2MNpmJk1l9nhsGTbeTLVVE+nr
- mcpVlMr+nyHIuYFJJMNqoLQMp5NIZkNBHIa1wMQiHYlMFXhZdA/MNIvlS1WA
- 7lJ49I8sdsm+ajlzt3CT8h1CdcptypzeWonuEvIKZyjXHp3DOSu3N5dkoId7
- FFs/CWiSFOBg5ffhdWIXz8on8LfnaH3A6llZ4Ngq421slz8k3RIJrB2i4uOq
- j2s+ruMGLbHuo4PuIViOm9ig/Rxujls56j8AJAtPiM0CAAA=
- """,
- """
- androidx/navigation/AbstractChildObjectComp.class:
- H4sIAAAAAAAA/5VSW2sTQRT+ZnLbbKNN66WJVau2FC/otsU3gxCDwsK6gg0B
- 6dNsdmin2czI7iT0MU/+EP9B8aGgIEHf/FHi2TVU0L64y57bfOc7nG/2x8/P
- XwE8xRbDI6Hj1Kj4xNNiqg6FVUZ73SizqRja3pFK4jfRsaTQjN/XwBi2Lmro
- y8yeNxXIEkO1o7SyzxlK9x8MGqig6qKMGkPZHqmM4XHwH7OfMTidYVIwuuA5
- jeOH+/1u2HvZwCW4dSpeZtgMTHroHUsbpULpzBNaG1swZ15obDhJEqJaCUbG
- Epn3WloRCyuoxsfTEqnCclPPDRjYiOonKs92KIp3acB85rq8xYtvPnO+f+Ct
- +WyP77AXNYd/+1jlTZ5D9xi2L1zwb61odDMU057RNjVJItMnI8uw/nairRpL
- X09VpqJEdv8sQgr2TCwZlgOlZTgZRzLtC8IwrAZmKJKBSFWeL4ruvpmkQ/lK
- 5Ul7QTz4hxa7JGG52LudK0r+NmVV8k3ynN5KkW1Q5uXqkK88PINzWhzfWYCB
- Du6SbfwGoE5UgIOl8+Y1QufP0hfwd2dofMLyaVHguFfYW9gsflC6KSJYPUDJ
- xxUfV31cw3UKseajhfYBWIYbWKfzDG6GmxmqvwBXhfge3QIAAA==
- """,
- """
- androidx/navigation/InterfaceChildClass.class:
- H4sIAAAAAAAA/41Qz2sTQRT+ZvJjk21qNqnWNPVXrdomBzct3pRiGxACsUJb
- ckhOk+yaTrOZhZ1J6DF/i2cvgiJ4kODRP0p8k4aCkIMs89775n37vTff7z8/
- fgJ4hT2GPaGCJJbBta/EVA6FkbHyW8qEyUcxCJuXMgqakdDaAWPwrsRU+JFQ
- Q/9D/yocGAcphp1VEhehNrcyDjIM2TdSSXPEkN7v1joMqf1apwAHeRdpuIRF
- MmRg3QIKWM+D4w5RzaXUDLX2f275msYMQ3NslUi/y1Bqj2ITSeW/D40IhBFE
- 4eNpit7PbMjbAJo7ovtraVGDquCA4WQ+K7u8whdnPnO5t+byXKoynx3yBjtZ
- L2c9XuWN1K9PWe6lz0q3KEfsajqX8bJW6ZBhd+X+/1hEa9EW3qmYNmNlkjiK
- wuTlyJAFzTgIGYptqcLTybgfJheiH9FNuR0PRNQRibR4eemex5NkEL6TFmyd
- TZSR47AjtaTusVKxWYzWOCB/0zQwS6dsDad3c6od5Cg+JXREmFN269+xVt/+
- huKXBWeXov0LeEYfsHnDgoeSdZIqq0bGk+7GUsu3BlPO1L+i+HmlTOGGsJTh
- eL6IO3hB+S317lLvXg+pFjZbuN9CBVtUotrCNh70wDQe4lEPjkZJ47FGQeOJ
- Rk6jrLHxF0rwRpzxAgAA
- """,
- """
- androidx/navigation/InterfaceChildClassComp$Companion.class:
- H4sIAAAAAAAA/51Sy27TQBQ9Y6d5mABpyyPhXQhSC6JuKhBIRUgQhGQpLRJU
- 2XSBJva0ncSeQeNJ1GVWfAh/0BUSCxR1yUch7jgB1mVzX+eee8fn+uev7z8A
- PMVDhmdcJUbL5CRUfCKPuJVahZGywhzyWHSPZZp0U57nXZ19bjvDFXVUwBga
- Qz7hYcrVUfh+MBSxrcBnKL+UStpXDP76Rr+OJZQDlFBhKNljmTM87/3Xxh2G
- znpvpG0qVTicZKF0DMXT8K045OPUdrXKrRnHVptdbkbC7Gz0A3hu82o7/gd+
- ygqUYfN80xiW/xB2heUJt5xqXjbxSUrmTM0ZMLAR1U+ky7YoSjoM7dk0CLym
- F3gNimbT6tkXvzmbbntb7E2l6p19LXsNz/VuMzfh8TkkquAmQ+2vTnSWPT6h
- 51uj01SYzZEl6bs6EQyXe1KJvXE2EGafD1KqrPR0zNM+N9Lli2I9UkqYYoGg
- gwUf9djE4p10WOvDWFmZib7MJTW/Vkrb4nk5OiR2ySlA3nN3pw+5S1noJCG/
- 9OgbqqcFfI9suSi+wBrZ+rwBNQRAg1F0YUF+Qt5bkOunhbyOcG1enBOK6CIu
- EebjPmVBQbqF22jhQbHwDtrFD08aUG/jAH6E5QgrEVZxhUJcjWjm9QOwHE20
- CM8R5LiRo/wbyZhkAy0DAAA=
- """,
- """
- androidx/navigation/InterfaceChildClassComp.class:
- H4sIAAAAAAAA/5VSXU8TQRQ9sy3dtizSFsVS/ABBLUXYghiNEBKs0TQpNUHS
- RHiatkPZdjtrdqYNj/wWn30hakg0McRHf5TxTqnwoInhYe+dc/fecz9//vr6
- HcAa1hgWuWyGgdc8ciXvey2uvUC6ZalFeMAbonTo+c2Sz5UqBd33NhhDqs37
- 3PW5bLlv6m3R0DYiDLP/otkVSl9Q2RhhiG140tObDNH83kKNIZJfqDmwkUgi
- iiRhHrYY2J4DB2MJWLhGrvrQUwxLlStUuk6pWkJvGTbKsccQ32j4w9xPrkA0
- bwSX5GHjBsNKvtIJNBG57X7X9UyM5L77Uhzwnq9LgVQ67DV0EG7zsCPC9fPu
- biYxiSxD4oKM4elV2rmsYt1BDtNmMrcY5ipB2HLbQtdD7knlcikDPSBSbjXQ
- 1Z7v0yDSf0reFpo3ueZks7r9CF0AMyJhBGjqHbIfeQYV6dVcYXh9dpxJWllr
- 8J0dJ63UaNKKR7NnxzP2qlVkz5n9YiwTS1k5qxj58SFmpaI76QsUp5BcND6S
- ihm6VVPvf6+EaqNSUlXep2HqMPB9ES53NMP0Tk9qryvKsu8pr+6Lrctm6UZK
- QVMwjFc8Kaq9bl2Eu5x8GDKVoMH9Gg89g4dGpyylCAfTFRScfBv0woZ45Zl/
- U8M8tb+yYIWmHqXqYqSnzBrovUTTipG+QzpjjpZ0hLCNOMllQpvkbZFOFk4x
- Wpj+gvETQhbcYSTwDEWSk+deSCFt9kEvw0brI96JIZdr1kR6pPAZ4x//SeOc
- Owxp4riOxDA4i8Gi4XzD5Dt2iqlPuH0ysESoNZOQDYrIUXOrA+5HeEy6RPa7
- xDizj0gZs2XcK2MO8/TE/TIe4OE+mEIeC/uIK6QVCgqOwqIyMKMwoZD7DYsK
- p/5yBAAA
- """,
- """
- androidx/navigation/InterfaceChildObject.class:
- H4sIAAAAAAAA/41STW/TQBB9u0kTxw00LV8J5asUUOkBtxU3KqQSgWQpGIlG
- kVBPG3tJN3F2JXsT9ZgTP4R/UHGoBBKK4MaPQsyaUA4gga2dmfd25u3O2N++
- f/wM4DEeMGwJnWRGJSeBFlM1EFYZHYTayuytiGX7WKXJq/5QxrYKxtAYiqkI
- UqEHwS+2xLDxN42uzO25ThVLDJV9pZV9ylDaetirowrPRxk1hrI9VjnDdud/
- 7/KEwduP00LOB3caXhgddg+i9vM6VlCvEdlg2OyYbBAMpe1nQuk8EFobW8jm
- QWRsNElTklrtjIwlseCltCIRVhDHx9MSjYg5U3MGDGxE/IlyaIeiZJcOmM98
- nzd5seYz7+s73pzP9vgOe1b1+Jf3Fd7gLnXP3eWfU6JzG5GYto22mUlTmT0a
- WYb11xNt1ViGeqpy1U/lwe8uaHZtk0iGlY7SMpqM+zLrCsphWOuYWKQ9kSmH
- F6R/aCZZLF8oB1oL4d4fstil+ZWp5Qqtlhso+TvUt8Nr5Dm99P0IbRAK3HDI
- L22fwT8ttu8ukoH72CRb/5mAZYpAhRfOi69RtnuWP4G/OcPFD1g9LQiOe4W9
- TRLuZ2W4RAKXj1AKcSXE1ZBKmxSiFeI61o/ActzATdrPUc9xK4f3Az/F2jnp
- AgAA
- """,
- """
- androidx/navigation/NavController.class:
- H4sIAAAAAAAA/41SW08TQRT+Zttut8utrYJQQeWiFFC2EH2xhERJjEtqNdL0
- hadpuynTbmeT3WnDY3+L/8AnjQ+G+OiPMp7ZNlBAo5vsuX/fnDNzfv769h3A
- c+wzrHLZCgPROnckH4g2VyKQTpUPjgKpwsD3vTANxpDt8AF3fC7bzvtGx2uq
- NBIM5oGQQh0yJIpb9WmkYNpIIs2QVGciYliv/JO9zGCNcx7hiu5WnSEVepHb
- YmAuw3yxcnX2iQqFbJd1zXolCNtOx1ONkAsZOVzKQMUHRE41UNW+75c1U9BX
- noUcw4NuoHwhnc6g5wipvFBy33GlZoxEM0rjDh3WPPOa3TH8Aw95z6NChs3J
- JkYXUP5TW9OYx4KNu7jHkL9dcGOaMZGeZvmg9vJ25rBYq8Xp/O0cQ64ynuid
- p3iLK04xozdI0NMyLTJagG6xS/Fzob0SWa09hvBiuGwbi4ZtZC+GtmFpg34r
- Sdqif9ZaWLwY7hsl9jr145NJyeOVbKJglJJrlnUxzKa2KbVvZs2C8XZUkD6e
- HRVQ1CKdmfCpqmTrk2nfdD812qdrS7DbVfT2R0GLVmCuIqRX7fcaXljjDd/T
- wwdN7td5KLQ/Dm587Eslep4rByISFLp8rVdXi8CQORFtyVU/JIh9EvTDpvdG
- aPzSGF8foSdAWIVBW6y/JHVLS01yhzxH9046tf0F1mcyDDwlacbBJJ6RnB4V
- IAObdA5TcUSDX8T1NP5NoBkDF0bJMVBbM5glqSnmKKcpyqR1VXonn/+KxetE
- JqwJovQlUZoolii/q23df3bcWAGJ/2G1/8q6THknrr5/nd1AKZbb2CNdoegK
- XcmDUyRcPHTxyKUbXiMT6y428PgULMITbJ5iKoIdoRjBjLRNxlaEXIRChJnY
- Lf4G70EG2roEAAA=
- """,
- """
- androidx/navigation/NavControllerKt.class:
- H4sIAAAAAAAA/41UW08TQRg9s1va7VJoy70F5Y6tFxbwLmhCmphsrCVBgiEk
- Jtt2rAvLbLIzbXjkt/gLVB5IJDHER3+U8duV2NCC0IedmTPnnD3z9Zv99fv7
- DwCP8JJh1hH1wHfrh5ZwWm7DUa4vrIrTKvlCBb7n8eCNSoAxZPaclmN5jmhY
- G9U9XiNUZxhscFXyHCltIZUjanyTf2SYLhTLl/lucfmXvUovLvtBw9rjqho4
- rpCWI4SvIpq0Kr6qND2PWJlal/nkNdYpGEgmocFkyHfGe++qT+tBIzIqXJfy
- nEwxRmtXmSzczCKFfqTDUBmGMQplC8GD7sJdFWmjqXgw1xZRpGH3covLA3UZ
- pDCE4TDQCIOxVvNc4apXDHqhuM1w679nSiDPEF+LFClMIGdiHLcY5m5SiQQm
- GWIFu7gdSqdNTGHmCmln5gTmTMyH9Gx531cU2XrLlVN3lEP10A5aOjU1Cx/J
- 8AEGth9ONNo8dMPZEs3qywwfzo7y5tmRqY1ppmboHaM2k80QQVtiPz/HDeLl
- Y4aW0QmNEdjTBuOZBIEGgck2aGZ6w7esMOSuPFQCDxnM9smozy9cucV9xTC+
- 2RTKPeC2aLnSrXp8vX1DqIQlv84Z0mVX8ErzoMqDLYc4DKm2LSee+c5vBjX+
- 2g33cueW212GWKZWiFGJdOTD+0PFe0qrOI0JGvNh+3Zh1EEdWAw59NBKwzNa
- TRAa/mLf0Psl+kOen3OBvgu6HFKEdKmynap0h2oAg92q0U5V9oLKwBgpWaQq
- IeoUzJ5ifOcEt4/Re4qpnUz6BLPHyJ5iPpovHGP06z/T/kjUB5PijJC5jhe0
- Nml3nr6pj8l8New6PMEajRuE36GiFHah2yjauGvjHu7beIBFGxaWdsHC8q/s
- IiVhSCQleiTiEv0SaRmCfRJDEsMSAxKDfwAPYTX9vQUAAA==
- """,
- """
- androidx/navigation/Outer$InnerClass.class:
- H4sIAAAAAAAA/41U31MbVRT+7m5+bJYENkBbfkRQiZiEtgvYai20CiiyGEIF
- h7HiyyVZw8Kyi7sbpr44PPVP6Iy+OOM4PvFQZxQcO+Ng++bf5Diem90mNVSG
- meSec8+e853vnnPu/euf3/8AcAPrDHnu1DzXqj3QHX5g1XlguY6+2ghML284
- jukt2Nz3k2AM2g4/4LrNnbq+urVjVoMkZIbErOVYwV2GWMEobjDIheJGGnEk
- VcSgMCiWQJnz6gzMSENFVwoS0uQfbFs+w3j5IgRmGLrqZmC0sCiNwaBW3b19
- 1zGdYIoAq+7+1wxF4nFRzLGy69X1HTPY8rjl+Dp3HDdoevt6xQ0qDdueEYdJ
- qMT5MkNapMjXzC95ww4YtgoXS2QY5c7azVyQYxp96BfZh6iUgbseeJZDx+8v
- FF+ADK10niudtvmGZddML4kRFaOiHf1t7MLzztxR8Bo1ku/vm06N4VrhLPTZ
- bBEyERxDXoC/wZATpT/P8U3hWBCOC+c7loTjRBo5vCK0a3T4be5vL7g1kyHb
- jjScwKyL802GA0gTpmNaxRTeohOZXzW4TTN2qfCS+n9Os39e+6n3fMs2qapx
- N9g2PYbesyhEprzrBrbl6CtmwGs84GST9g5kul9MLCmxgIZ/l+wPLLEjrlKN
- BvbH08MRVRqQVEk7PVTpJ2mKKikJkl0kZZLdytOHysDp4bQ0yeYzvQlNGpIm
- 5ac/JCQttpzSkmK39OyhvNynKaSTo6JIoROZGZlTpKvTitY1FBtgk2zp2SOZ
- AtOhxyNGeob0bqGvZVvwCtEZiilxLSG4TjNxgsH/HdgkFukutieL3ooKP1hw
- ncBzbdv0ru/SZYmFzespW45Zaextmd6nor6irG6V2xvcs8Q+Mg6vNZzA2jMN
- 58DyLTLNtXvDkFkPeHV3he9H3vlO73vc43smUftPWLpN0aStuu42vKq5aAmI
- wQhi40w6GiaJ3jJRgl7xfpGmkE6vAq3LtFuk7xJJtXSCVGn4V2R+pp2Ej2nt
- huj4CMWPIkWyTLvLoTd96xGzQZpApVGCRv8QUxcjQzJe+gWZoxZcomkcbcKk
- Q4cIJkvkngePdQazlwbQy0KwImCKWApOqSeQ7g+f4MrjVlBINtUim4rIrkRs
- LgFaCgMYjHKPR8XK5mLffAtFMJgtDR9jOISs0CqDCQS621H62yQFtdwTjN4/
- wau9rx9jXEQeo6gVj3H1GNcfdxwjFzF6gQeteqsG41ENmgx+w43OMihRPMNN
- vB3x+IKkaFe+NPET4rGjiT8hfYe4fDRxCmlFAF2l//fCEgt7Umm2T04qfyOb
- pH27YvlWxfK4hXcpzyrpSUHqnWYN7jVD6XrhIyxR+T5pAhpYI/kZ2W9Tp2Y2
- IRuYNXDHwF28RyreNzCH+U0wHwv4YBM9vvh96ENtrgkfmo+sj14ffT5uNo23
- fOg+cqT/CwA97z/6BwAA
- """,
- """
- androidx/navigation/Outer$InnerObject.class:
- H4sIAAAAAAAA/41US08TURT+7p0+plMe5SFvnxR5KVMQVxATJBqHlGKEYJTV
- bTvCwDCjM7cNS1bu3Lpw6cIVC4kLEk0MStz4o4jnTkdBCMakvec7Z84535nv
- 3Pbn8eevAKZxl2FIeNXAd6o7pifqzrqQju+ZSzVpB3nL8+xgqbxpV2QajCG3
- KerCdIW3bv6OagypWcdz5D0GbWR0tQlJpAwkkGZIyA0nZBgu/hfDDIMu/WUZ
- ON46Q+fIaPGErRGljMGiH6ybm7YsB8LxQlN4ni+jhqFZ8mWp5rqUlT3VVkcL
- Nd4Q4ca8X7WjIS3tx/TxGxrcflUTLk14aaR49s1mRp8z5P/FRlSi7NpEl/Tl
- hh0wtJ/vQtSzFTfSxwBXouhWaXllrjT/oAl9MDIU7GdoK275ktLMRVuKqpCC
- Cvl2XaMdMXVk1AEGtkXxHUd5BULVSYYXh7tXDN7DDZ473DW4rkA2trqhQrkW
- /ei10XO4O8UL7H5a59/fp3iOL3TktD5eSEzpuWRfoocV2KOjt9pCJpeiaJow
- I6wTziis2KaYmqH3wm2mMUp3pCTq874nA9917WBiSzL0P6l50tm2La/uhA5p
- NneiI92Sxl5ai45nl2rbZTtYUboqOf2KcFdF4Cg/DjYvS1HZWhQvYz9/tvdj
- EYhtm6b5i6QpuhHzrghDm1xj2a8FFfuho1r0xi1Wzw2HSVpPIlK+V22L7C3y
- UmSbySbpaTLybpNnqv2o6NgB9H0CHBNxMjBAj4GmRgIy1Eo1zVKER8XX42Kt
- vfVj9OgkXYvTTzOTzGiLeU9K2/cuKGXoQGfMZJHlZLvHxj8gmdgb/wb+Dklt
- b/wQ/GliLxq8QGcCPK1HzboaBXEzhbroy0gdqBtNvx8COnr+SNEdFQDZL+DP
- DtD7CQP7UUDDFJ1KR44xtJCqdyK+cforUqMxXCZ5rqxBs3DVwjWL3u4GQQxa
- yGNoDSzETQyvwQjVZyREKkRHBLpC5CKQpfMXu5HXfeAEAAA=
- """,
- """
- androidx/navigation/Outer.class:
- H4sIAAAAAAAA/4VRW2sTQRg9M5vLZhNtGi9NjK2XptpUcNviUy1CDQoLMYW2
- BCRPk2SIk2xmYXcS+pgnf4j/oPhQUJCgb/4o8dttNA9S3GG/M9/tfDNnfv76
- 8g3ACzxjqAjdDwPVP3e1mKqBMCrQ7vHEyDALxlAciqlwfaEH7nF3KHsmC4sh
- c6i0Mq8YrO16u4A0Mg5SyDKkzAcVMVSb17K+ZLAPe37S74DHTbbXOj07ajXe
- FHADTo6CNxk2m0E4cIfSdEOhdOQKrQOT8ERuKzCtie8T1WpzFBgic99JI/rC
- CIrx8dSi27HY5GIDBjai+LmKvV3a9fcY6vNZweFl7vDifOZw27J/fOTl+Wyf
- 77IDbqVeZ23+/VOGF3ncsM9iGsfTWoYNX0R0yXziXKnCULv2xrVlUxYPGLb+
- U/lH50ekfktMG4E2YeD7Mnw+ojnVk4k2aiw9PVWR6vryaCkM6d8I+pJhpam0
- bE3GXRmeCaphKDWDnvDbIlSxvwgWlieT1OycBpOwJ9+qOFdZzGn/MwV79EKp
- RNZK/GCENfIyhEVCTiudeFvkubH4hOmdS9gXSfrJohio4inZwlUBckQF2Mj/
- bV6j6vjLfwV/f4nCZ6xcJAEL22RLlH5I/zqd4zHhBmE9GbGJHcIDolkl4lIH
- lodbHm57uIO7tMWahzIqHbAI91DtIB3BiXA/QibCeoSN33FlSFUiAwAA
- """,
- """
- androidx/navigation/OuterComp$InnerClassComp$Companion.class:
- H4sIAAAAAAAA/51TTW/TQBB9a6d2YkJJUz4SoHwGSBHUTQUIqQgJgpAipa0E
- KJce0CZZyib2Gq3XUY858UP4Bz0hcUBRj/woxKwTqLgglcvMm3nzZr0z3h8/
- v30H8AhNhidcDXUih4eh4hN5wI1MVLiXGaHbSfyp0VGKUMTTNA+t4YpKfDCG
- yohPeBhxdRDu9UdiYHy4DN4zqaR5zuA213tlLMELUIDPUDAfZcrwtPt/R24z
- tJrdcWIiqcLRJA6lIoniUfhKfOBZZNqJSo3OBibRO1yPhd5e7wVw7NGrjcEJ
- +T7OWYaN03VjWPkt2BGGD7nhlHPiiUvDZNaUrAEDG1P+UNpok9CwxdCYTYPA
- qTmBUyE0mxaPP7u12XTL2WQv/aJz/MVzKo6t3WK2w4PTzMjHFYa1fyp8rDEs
- /y1jKP0ZLi1zl0/ozkYnUST0xtjQwtrJUDCc60oldrO4L/Q73o8oU+0mAx71
- uJY2XiTLJ90FrTl4m2R6IF5Ly9XfZMrIWPRkKqn4hVKJyb8wRYs2VLBjI+/Y
- v4Vuf4ui0M6R/NL9ryge5fRtsl6efIwG2fK8ACUEQIUROrMQPyTvLMTlo3wn
- VnBxnpwLcnQWy8S5uENRldiruIbrqOfoBvm7+cE3cS9/LzQL0lT24Xaw0kG1
- g1WcJ4gLHep9aR8sRQ114lMEKS6n8H4BDr3L2mwDAAA=
- """,
- """
- androidx/navigation/OuterComp$InnerClassComp.class:
- H4sIAAAAAAAA/5VUW0/cVhD+jvfmNQt4IRcCmyYt23S5BHNLSoE0XFKK6QIp
- pDSE9uGw64LB2NT2ovSlylN+QqT2pVIf+sRDorZQFamiyVt/U1V1jm2WW4S0
- 0u45M+OZb74zM+f889+ffwEYxNcM3dwuu45ZfqrZfMdc477p2Np8xTfcSWdr
- O6/bNkkW9zyhpsAY1A2+wzWL22va/OqGUfJTiDEkR03b9D9miBf0jiWGWKFj
- KYMEUgrikBlkUyCNu2sMTM9AQV0aEjLk76+bHkNPsRYiIwx1a4avVzEpnc6g
- lOibYxu230fAJWf7O4Y+4lMrdnvRcde0DcNfdblpexq3bccPojxtzvHnKpY1
- Ig6XVOgMVxgyIlW+bHzDK5bP4BZqS6jrxbM1HamRcwbNuCTYtFKpfWfRd02b
- ynKp0HECOrTS+a6etU1UTKtsuCm8o+CGaFfLafzCUffuyXiXms23tw27zHC7
- cB7+fMYInUi2Iy8SvM+QE225yPED4VgQjpMXO3YKx64McrgupNtUgHXurU86
- ZYMhexyp276xJs7YGw4pTaGGfgV9GKATGd9WuEVzeLnwll48YchfNBI0D3zV
- MqiyCcdfN1yGpvMoxGu0ZEW35G4t3c2LhdvkksKImOjipuMTkraxs6WZdCzX
- 5pb2IBy/SWLku5WS77iz3N2kIoUX8Z6CUVDmdBWMYaimITumQXUfw7i4wBNU
- 4iM2s4bPy9znRFHa2onRC8PEkhYL6Npvkv2pKTTqgFSmK7p7+OymIrVIiqQe
- PlPoJ6myIslJ2utoj9HeQGb59XO5hVwb+6VeNswaJ+qbkqrUKvXGXv+clNT4
- TFpNCW36zfPYTLMqk3z4rF+WpdCJzIzMaZKVflmta423sF42/eZFjAIzoccL
- RnI9yQ1CXshW4WXK3xqXE2pScO5n4iTXL6xaCg8ZGk6Xjl7NOb5DrfFdx7IM
- t2eTnom2hYrtm1uGbu+YnknzM348UzSi4QA3Fk3bmKtsrRruIzFjYrScEreW
- uGsKPTLWL/q8tDnLtyM9fxb7IXf5lkEUTyXJHNM0SFUWnYpbMqZMAXEtglg6
- R46ujESvOmi9JgaBSvKItCTtl2lvEq+7aDzpicD6BWlT5C3RrnTuI93Z9jvq
- XwUIS7Q2QEzFAGEOUtQAviTtSuhN3xrF/JAkUGncoNI/xNTEWNGe6PwN9btV
- uGRgHAxgMqFDBJMlckfB7WeD2VsD6F0lWBHQRywFp/QBpOW2fVx9WQ0Kyaar
- ZNMR2RNlUdNooXKFuW9FBczm4t//AFkwGO1s20NbCPmY1hiYQKBXLUo/TLug
- ljvAjeV93Gx6bw+3ROQeOtSOPXTvoeflmWPkIkYn28OobNkqj7AGAYM/MHi2
- DHIUz3AHdyMeX9Eu2pXv7PoFifhu19+QfkQittt1CGlWAHXT/ydhiYc9eRy0
- L5aS/0U2RfpxxfLViuUxhI8ozzLJKUHqwyD9MFIR1ZYgKRE7wOgy28f9XzH5
- KrDE8CSYOjFfn2OBijxK0hjtK0H6RaIMkhkeUF8/WUFMx5SOT3VMQycRMzo+
- Q5EcPMxibgWqh0YP8x6UYE16wpL10OSh2cOdwDjkQfOQC+Sx/wHugknKUQkA
- AA==
- """,
- """
- androidx/navigation/OuterComp$InnerObject.class:
- H4sIAAAAAAAA/41US08TURT+7p0+plMe5SFPxQeolCpTUFcQE2w0DinFCMEo
- q0s7wsB0BmduG5as/AkuXLrQDQuJCxJNTJWdP8p47jAKQiQm7T2POef7znzn
- tj9+fv4K4C7uMeSFVwt8p7ZjeqLprAvp+J652JB2UPLr22OW59nB4tqmXZVp
- MIbcpmgK0xXeuvk7qzGkZh3PkfcZtPH8ShuSSBlIIM2QkBtOyFAo/zfLDIMu
- /SUZON46Q+94vnzMeJSlitGyH6ybm7ZcC4TjhabwPF9GoKFZ8WWl4bpUlT0B
- q6ODgDdEuFHya3Y0qKV5H4qzNLz9qiFcmvLCePn0283kXzCMncdGVGLNtYku
- 6csNO2DoPotC1LNVN9LIAFfC6FZlaXmuUnrYhiEYGUoOM3SVt3xJZeaCLUVN
- SEGNvN7UaFdMHRl1gIFtUX7HUVGRvNoUw8vW7ojBB7jBc61dg+vKycZWN1Qq
- 16EfvjYGWrvTvMgepHX+/V2K5/h8T04b4sXEtJ5LDiUGWJE9PnyjzWdyKcqm
- yWfk6+RnlK/Yppma4dK5G00jT3elIpol35OB77p2MLklGYafNjzp1G3Lazqh
- Q7rNHWtJt+VoN51lx7MrjfqaHSwrbZWkflW4KyJwVBwn25ekqG4tiO04HjuN
- /UQEom7TRH+RtEW3ouSKMLQpNJb8RlC1HzkKYjCGWDkzHKZoRYlI/UG1MbK3
- KEqRbSebpKfJKLpNkal2pLITB9D3yeGYjItBQCadbUcFyBCUAs1ShkfNV+Nm
- rbvzY/TouFyLy08yk8zoinmPW7v3/tHK0IPemMkiy8n2TxTeI5nYK3wDf4uk
- tldogT9L7EWDF+lMgKf1CKzvqCEGU14ffRm9FNStpt8QOToG/kjRHzUA2S/g
- zw8w+AkX96OEhmk6lY4cE+ggVe9EfAX6W1Kj0Q0jeUZWoVm4bOGKRW93jVyM
- WhjD9VWwEDdwcxVGqD7jIVIheiKnL0QucrJ0/gKE9w8Q7AQAAA==
- """,
- """
- androidx/navigation/OuterComp.class:
- H4sIAAAAAAAA/41RW2sTQRT+ZjaXzWZt03hpYk2rtmpTxW2LT7UINSgsxC20
- JSB5miRLnGQzK7uT0Mc8+UP8B8WHgoIEffNHiWe30SJCcYc935zbd+ac8+Pn
- 568AnuExQ02oXhTK3qmjxET2hZahcg7H2o8a4eh9HoyhNBAT4QRC9Z3DzsDv
- 6jwMhty+VFK/YDA26y0bWeQsZJBnyOh3MmZYa17J/JzB3O8GKYcFniSarnd8
- cuA1Xtm4BqtAxgWG9WYY9Z2BrzuRkCp2hFKhTrlixwu1Nw4ColpqDkNNZM4b
- X4ue0IJsfDQxqEuWiEIiwMCGZD+VibZNt94OQ302tS1e4RYvzaYWNw3z+wde
- mU13+Tbb40bmZd7k3z7meIknCbssoVlwlaI2AhHHSS8MxdRwMR2GJ1d2vvF3
- ch5r9Ij/yPg9+3u0EU9MGqHSURgEfvR0SDVXjsZKy5HvqomMZSfwDy4HRTtp
- hD2fYbEple+NRx0/OhEUw1Buhl0RtEQkE31utC9f6FOydRyOo67/Wia+6rxO
- 658q2KGNZdIxV5MFEm6QliMsEXI62VR7QJqTLIMwu3UO8yx1P5wH02rwiKR9
- EYACUQEmin+Slyk6+YpfwN+ew/6ExbPUYGCTZJncd+mv0TvuE64S1tMS69gi
- 3COaJSIut2G4uO7ihoubuEVXLLuooNoGi3EbK21kY1gx7sTIxajFWP0Fraxj
- CzoDAAA=
- """,
- """
- androidx/navigation/TestAbstract.class:
- H4sIAAAAAAAA/4VRy0oDMRQ9SduxjlWnPusL1IWvhaPiThFUEApVQaUbV2kn
- aOw0gUlaXPZb/ANXggspLv0o8WZ07+ZwHjfh5Obr+/0DwCFWGFaFTjKjkudY
- i756EE4ZHd9J605b1mWi7UbAGKIn0RdxKvRDfN16kt4tMATHSit3wlDY2m5W
- UEIQoogRhqJ7VJZhvfHf5UcM1UbHuFTp+FI6kQgnyOPdfoEKMg+jHsDAOuQ/
- K6/2iCX71H04CENe4yGPiA0H5Y3acHDA99hZ6fMl4BH3cwfMn46uRP/caJeZ
- NJXZbsdRyXOTSIbJhtLyqtdtyexOtFJyphqmLdKmyJTXf2Z4a3pZW14oLxZu
- etqprmwqqyg91dq4/HG2uAZOO/ir7FdCWCMV5xoo7byh/EqEY4EwyM1NLBJW
- fgcwijDPl3Kcx3L+VwxjlFXuUahjvI6JOiYREUW1jilM34NZzGCWcovQYs4i
- +AEAejLS6AEAAA==
- """,
- """
- androidx/navigation/TestAbstractComp$Companion.class:
- H4sIAAAAAAAA/5VSy24TMRQ99qR5DAHSlkfCuyVILRKdpGJFEVIJQoqUFolW
- 2XSBnIkpTmY8yHaiLrPiQ/iDrpBYoKhLPgpxPQmwpZv7Ovfcax/756/vPwA8
- xxOGHaGHJlPDs0iLqToVTmU6OpbW7Q+sMyJ2nSz93PRGaIJKYAy1kZiKKBH6
- NHo3GMnYlRAwFF8qrdwrhmBru1/FCoohCigxFNwnZRlavcut2mNob/XGmUuU
- jkbTNFLaSaNFEr2RH8UkoXZNvEnsMnMgzFiave1+CO5Xrjfjf+CHNEfprpeb
- xrD6h3AgnRgKJ6jG02lA4jFvKt6AgY2pfqZ81qJo2GZozmdhyOs85DWK5rPy
- xZegPp/t8hZ7XSrzi69FXuO+d5f5Cc3/0aaEuwyVvwLRQxyKKZ3bmSxJpNkZ
- OxK7kw0lw/We0vJwkg6kORaDhCprvSwWSV8Y5fNlsdrVWppOIqyV9EThUTYx
- sXyrPNZ4P9FOpbKvrKLmfa0zl5/Lok0qF/zVyXP/0nSDh5RFXgvyK0+/oXye
- w4/IFvPiC2yQrS4aUEEI1BhFV5bkZ+T5klw9z3X1hFuL4oKQR1dxjbAAm5SF
- Oeke7qOBx/nCB2jmf5s0oN7aCYIuVrtY62IdNyjEzS7NvH0CZlFHg3CL0OKO
- RfE3/dAjtxgDAAA=
- """,
- """
- androidx/navigation/TestAbstractComp.class:
- H4sIAAAAAAAA/41RW2sTQRg9s5vrurFJvSXWS2trTPvQbYsgNEWoESGQpqAl
- IHmaJGOdZDMrM5PQx/4W/0HxoaAgwUd/lPjtNrYPvuTlO/Ndzvku8/vP958A
- XmKLYYOrgY7k4CxQfCpPuZWRCk6EsYc9YzXv20Y0/pIFYygO+ZQHIVenwXFv
- KPo2C5chcyCVtK8Z3Npmx0caGQ8pZBlS9rM0DNXWIg3qDLmDfjiX2l6EshEb
- riiVhc+wW2uNIksKwXA6DqSyQiseBm/FJz4JiaCIOenbSB9xPRK6fjXsbQ8F
- LDHkr8UYdhaa+KZ93UcJy3k4uMOw3or0aTAUtqe5VCbgSkU2UTBBO7LtSRjS
- rqV/sx4Jywfccoo546lLn8Jik48NGNiI4mcy9nboNdile87Ofc8pO55TnJ17
- Ts7JVcuz81V3z9lh+8x9k/71NeMUnbh6j8UaxTaf0vpWR2Eo9PbIMqy8nygr
- x6KpptLIXigOb6akj2tEA8Gw1JJKtCfjntAnnGoYlltRn4cdrmXsz4N+Uymh
- GyE3RhDZ+xBNdF+8k3GuMu/T+a9Lao3OlUp2rMTXI1wnL0N4j9AhTCfeBnlB
- fAnC9NYlchdJ+vm8GNhHlax/VYA8PMIcbl2Ty0huCf8HCh/ZJYrfcPciibh4
- QdajugIplmiQWqL9DJuEryh+nxQfdOE2UW6i0sRDrNATj5p4jCddMIOnWO0i
- ZeAZrBlkDEp/AVeTkqlcAwAA
- """,
- """
- androidx/navigation/TestClass.class:
- H4sIAAAAAAAA/31Ry0oDMRQ9N7VTHauO7/reqgtHxZ0iaEEoVAWVblylnaCx
- 0wQmaXHZb/EPXAkupLj0o8Q7o2s3h/O4CecmX9/vHwCOsEHYkCbJrE6eYyMH
- +kF6bU18p5yvp9K5CogQPcmBjFNpHuLr9pPq+ApKhOBEG+1PCaXtnVYVZQQh
- xlAhjPlH7QhbzX9vPibMNrvWp9rEl8rLRHrJnugNSlyNcpjIAQTqsv+sc7XP
- LDkgbI6GYShqIhQRs9GwNhoein06L3++BCIS+dQh5WejKzmoW+Mzm6Yq2+t6
- 7le3iSLMNLVRV/1eW2V3sp2yM9e0HZm2ZKZz/WeGt7afddSFzsXKTd943VMt
- 7TSnZ8ZYX+zlcADB6/8Vzl+DscYqLjRQ3n3D+CsTgRXGoDCXscpY/R3ABMIi
- XytwGevFHxEmOaveo9TAVAPTDcwgYorZBuYwfw9yWMAi5w6hw5JD8AOsqUxn
- 4AEAAA==
- """,
- """
- androidx/navigation/TestClassComp$Companion.class:
- H4sIAAAAAAAA/5VSy24TMRQ99qR5DAHSlkfCuxCkFminqdgVIUEQUqS0SKXK
- pgvkJKY4mbGR7URdZsWH8AddIbFAUZd8FOJ6UmCJurmPc++5987x/Pz1/QeA
- 53jM8FTooTVqeJJoMVXHwiujk0PpfDsVzrVN9rkZjNCEl8AYaiMxFUkq9HHy
- rj+SA19CxFB8obTyLxmi9Y1eFUsoxiigxFDwn5Rj2OxeYM8uQ2u9OzY+VToZ
- TbNEaS+tFmnyRn4Uk9S3jXbeTgbe2D1hx9LubvRi8LBvtTn4V/yQ5VWGrYtN
- Y1j+Q9iTXgyFF4TxbBqRbCyYSjBgYGPCT1TItikathia81kc8zqPeY2i+ax8
- 9iWqz2c7fJu9LpX52dcir/HQu8PChLX/ClPCbYbKX3XoCfbFlI721qSptFtj
- TzK3zVAyXO0qLfcnWV/aQ9FPCVnpmoFIe8KqkJ+D1Y7W0uYLJD1O/N5M7EC+
- VaHWOJhorzLZU05R8yutjc+PcmiRxIXw3eR5eGM6/z5lSRCC/NKTbyif5uUH
- ZIs5+AxrZKuLBlQQAzVG0aVz8iZ5fk6unuaiBsKNBbgg5NFlXKFahIeUxTnp
- Du6igUf5wnto5r80aUC9tSNEHSx3sNLBKq5RiOsdmnnzCMyhjgbVHWKHWw7F
- 3/peHcMPAwAA
- """,
- """
- androidx/navigation/TestClassComp.class:
- H4sIAAAAAAAA/4VRXWsTQRQ9s5vPdWOT+pVYP1obta0f2xRBsEXQiBBII2gp
- SJ8myVgn2czIzCT0sb/Ff1B8KChI8NEfJd7dxvbBh7zcM/fMuWfuvfP7z/ef
- AJ5hg2GFq77Rsn8UKT6Rh9xJraI9YV0z5tY29ehLHoyhPOATHsVcHUbvugPR
- c3n4DLkdqaR7yeCvre+HyCIXIIM8Q8Z9lpZhtT3XfZuhsNOLZz6P5urrSeCK
- +DxChsZae6gdlUeDySiSygmjeBy9EZ/4OHZNrawz457TZpeboTDbZ21eDlDC
- AkPx3IzhyfxeL97eDlHBYhEeriRTanMYDYTrGi6VjbhS2qXlNupo1xnHMU1Z
- +dfornC8zx0nzhtNfPoIloRiEsDAhsQfySTbpFO/wVCfHoeBV/UCrzw9DryC
- V50eL/tb3iZ7wfzX2V9fc17ZS7RbLHEod/iEJndGx7EwT4eOYen9WDk5Ei01
- kVZ2Y/Hqokf6rabuC4aFtlSiMx51hdnjpGFYbOsej/e5kUk+I8OWUsKkSxFU
- HHzQY9MTb2VyV5u9s//fK2jQsjLphLVkd4SrlOUIrxF6hNk0q1MWJXsgzG6c
- onCSXt+fiYHHeEAxPBOgiICwgEvnxVWkm0T4A6WP7BTlb7h6kjI+HlIMSFci
- xwo1spZ638M64XPir5PjjQP4LVRbqLVwE0t0xK0WbuPOAZjFXSwfIGMRWKxY
- 5CwqfwEoeIqnTgMAAA==
- """,
- """
- androidx/navigation/TestClassWithArg.class:
- H4sIAAAAAAAA/41QTW/TQBScXSdOYhLihK805ZsKtTngtOIGqgiRkCyFIpUq
- HHLaxJa7jbOWvJuox/wWzlyQQEgcUMSRH4V461ScOCDZ897sjuf5za/f338A
- eI49hj2hojyT0WWgxEomwshMBWexNsNUaP1BmvNBnlTAGPwLsRJBKlQSvJte
- xDNTgcPgvpRKmmOG0n54MGZw9g/GdZRR8VBClbjIEwYW1uHhWg0cdZKac6kZ
- no7+Z/YLmpHEZmBtyDxkaI3mmUmlCt7GRkTCCJLwxcqhlZiFmgXQ0DmdX0rL
- +tRFhwzDzbrt8Q73uL9Ze/Rwv+rxqtPZrI94n71utF2fd3nf+fnR5X7ptPWX
- VUndLVXLvmutjpgd4J+I1TBTJs/SNM6fzQ2tNsyimKE5kio+WS6mcX4mpimd
- tEfZTKRjkUvLrw6999kyn8VvpCU7p0tl5CIeSy3pdqBUZopINA4pt1KxU9vG
- SB2nvgyX8AGxY+Kcqtf7hlpv9ysanwvNQ0KrAXbwiPD2VoXraNqIqLNulCh8
- erdegU2Oarn3BY1P/7SpbwVXNhyPC7yPJ1RfFT9Zxo0JnBA3Q9wKaewdatEJ
- 6fvuBExjF3cnqGg0Ne5peAW6Gr5G6w+psmgInQIAAA==
- """,
- """
- androidx/navigation/TestClassWithArgComp$Companion.class:
- H4sIAAAAAAAA/5VSS29SQRT+Zi6FckWlrQ/wVR+YUBO5hXRXY1IxJiS0Jtrg
- ogszwEgH7p0xcwfSJSt/iP+gKxMXhnTpjzKeuaBu7ea8vvOdM/PN/Pz1/QeA
- PTxlaAk9tEYNzyItZmoknDI6Opapa8ciTT8od3pgR22TfK55IzTBBTCG8ljM
- RBQLPYre9sdy4AoIGPIvlFbuJUNQ3+mVsIZ8iBwKDDl3qlKGve7l1+0zNOvd
- iXGx0tF4lkRKO2m1iKPX8pOYxq5tdOrsdOCMPRR2Iu3+Ti8E92u3aoN/4Mck
- Qxkal5vGsPGHcCidGAonqMaTWUAiMm+K3oCBTah+pny2S9GwyVBbzMOQV3jI
- yxQt5usXX4LKYt7iu+xVYZ1ffM3zMve9LeYn1P9XnwLuMhT/ikQPciRmdHZn
- TRxL25g4Er1thpLheldpeTRN+tIei35Mlc2uGYi4J6zy+apY6mgtbbZH0lOF
- 783UDuQb5bHqu6l2KpE9lSpqPtDauOxsKZqkdM5fnzz3L0632KYs8nqQX3v2
- DevnGfyQbD4rNvCIbGnZgCJCoMwourIiPyfPV+TSeaatJ9xaFpeELLqKa4QF
- eExZmJHu4T6qeJItfIBa9s9JA+otnyDoYKODzQ62cINC3OzQzNsnYCkqqBKe
- IkxxJ0X+N9Gij4okAwAA
- """,
- """
- androidx/navigation/TestClassWithArgComp.class:
- H4sIAAAAAAAA/41S308TQRD+9vrrehY5KmIBf6CglqpcITwJIcEa4yWlJkhq
- DE/bdi3bXvfM3rbhkb/FZ1+IGhJNDPHRP8o4d634oA8kdzM7szPfzHyzP399
- /Q5gE+sMZa46OpSdY0/xkexyI0PlHYjI1AIeRW+kOdrV3Vo4eJ8DY3B7fMS9
- gKuu96rVE22TQ4ohuy2VNDsM6bK/2mRIlVebBWSQc5CGTTbXXQbmF+DgSh4W
- ChRqjmTEUKlftv4W1ekKsxtDUQGfwd5uB5PCG5dFWYkFV3SdwzWG9XK9HxpC
- 8XqjgSeVEVrxwHsu3vFhYGqhiowetk2o97juC701nuu6g1nMMeQvwBg2Lz3I
- 3xa2CihhPiZkgWG5Huqu1xOmpblUkceVCk2CEnmN0DSGQUAUzPzpd08Y3uGG
- k88ajFK0ThaLfCxAZPfJfyxjq0qnDm365flJ0bFKlmO55ycOfZZrO5adLp2f
- LOU2rCp7ynLPpopZ11qwqqkfH7KWm96fubBsSllI2xk3G+NtsLiK2+AjIsno
- MAiEXusbhsX9oTJyIHw1kpFsBWL37xy09VrYEQzTdalEYzhoCX3AKYahWA/b
- PGhyLWN74iz4Sgmd8Cco2XkdDnVbvJDx3fykTvOfKlgnQtM0uIX5mF/qs0JW
- lvRN0sX4EZJOkZ1JvI/I2qFoi7RTOUO+svgFU6cJwuNJJrCGJyTnxlG4iumY
- aDrFaLQXuPSPsbyYf9KZymdMffwvTGEcMIGxqancJLmEZIMofMPsW3aGG5+w
- eJp4UpQbF2T0+KxkMC/BXkWVdI38twjx9iFSPu74WPJxF/foiGUfK7h/CBbh
- AR4ewo4wHaEcwUlkNoIbYSZC6Tedt709GAQAAA==
- """,
- """
- androidx/navigation/TestGraph.class:
- H4sIAAAAAAAA/31SQWsTQRT+ZpJsNttoY6s2sdaq7UE9uG3xZhFqUFmIK9gQ
- kJ4m2SGdZDMju5PQY07+EP9B8VBQKKHe/FHimzXoQXAW3nvfN998zHuzP35+
- vQTwDLsMW0InmVHJWajFTA2FVUaHXZnbN5n4eFoFY2iMxEyEqdDD8F1/JAe2
- ihKDd6i0si8YSo8e9+qowAtQRpWhbE9VzrDd+a/zcwb/cJAWHgG4O+hH8XH3
- KG6/quMaghqR1xl2OiYbhiNp+5lQOg+F1sYWXnkYGxtP05SsbnTGxpJZ+FZa
- kQgriOOTWYm6ZC7UXAADGxN/phzaoyrZZ9hdzIOAN3nAG1Qt5v73T7y5mB/w
- Pfay6vOrzx5vcKc9YM6hEYtZ22ibmTSV2dOxZdh8P9VWTWSkZypX/VQe/b0j
- jaNtEsmw2lFaxtNJX2ZdQRqGtY4ZiLQnMuXwkgyOzTQbyNfKgdbSuPePLfZp
- OuWipZYbFuV7hDx3QcqcvkqBtgmFrnHKlScX8M+L7ftLMbCOBxTrvwWokRXg
- Y+XP4Q1Su7XyDfzDBepfsHpeEBwPi7iFneJfokcgg7UTlCKsR7gZ4RZuU4mN
- CE20TsBy3MEm7ecIctzN4f0CEKx6togCAAA=
- """,
- """
- androidx/navigation/TestInterface.class:
- H4sIAAAAAAAA/4WOz0rDQBDGv9lo08Z/qVqoR/Fu2tKbJykIgaqg4iWnbbIt
- 22x3IbsNPfa5PEjPPpR0Ux/AGfjmmxn4zfz8fn0DGKNHuOW6qIwsNonmtVxw
- J41OPoR1qXaimvNchCBCvOQ1TxTXi+R1thS5CxEQutPSOCV18iwcL7jjDwS2
- qgMPp0Y6jYBApZ9vZNMNvCuGhN5u245Yn0Us9m7e321HbEDNckS4m/77lb/k
- wfELrydGu8ooJar70hGid7OucvEklSDcvK21kyvxKa2cKfGotXEHmG35UzjC
- XzBcHfQS174OPfjYZytDkCJM0U7RQeQtTlKc4iwDWZzjIgOziC26e5qGvyhR
- AQAA
- """,
- """
- androidx/navigation/TestObject.class:
- H4sIAAAAAAAA/32Sz2sTQRTHvzNJNptttLH+aGK1VtuDenDb4s0i1KCwEFew
- IVB6mmSHOMlmBnYnS485+Yf4HxQPBQUJevOPEt+sUQ+Cu/De+37nzYeZt/v9
- x6cvAJ5ij2Fb6CQzKjkPtSjUWFhldNiXuX0znMiRrYMxtCaiEGEq9Dj87VYY
- vCOllX3OUHn4aNBEDV6AKuoMVftO5Qw7vf+jnzH4R6O0hATgbqcfxSf947j7
- sokrCBpkXmXY7ZlsHE6kHWZC6TwUWhtbwvIwNjaepymhrvWmxhIsfC2tSIQV
- 5PFZUaF7MhcaLoCBTck/V07tU5UcMOwtF0HA2zzgLaqWC//be95eLg75PntR
- 9/nXDx5vcdd7yByhFYuia7TNTJrK7MnUMmy9nWurZjLShcrVMJXHf89I8+ia
- RDKs95SW8Xw2lFlfUA/DRs+MRDoQmXJ6ZQYnZp6N5CvlRGcFHvyDxQFNp1pe
- qeOGRXmblOcOSJnTWyvVPVKhuzjl2uNL+Bfl8s6qGbiJ+xSbvxrQIBTgY+3P
- 5k3qds/aZ/DTSzQ/Yv2iNDgelPEudsu/iT4CATbOUIlwPcKNiNC3qMRmhDY6
- Z2A5bmOL1nMEOe7k8H4CjO1ti4oCAAA=
- """
- )
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongStartDestinationTypeDetectorTest.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongStartDestinationTypeDetectorTest.kt
index eaaf466..7b31a26 100644
--- a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongStartDestinationTypeDetectorTest.kt
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongStartDestinationTypeDetectorTest.kt
@@ -17,22 +17,15 @@
package androidx.navigation.runtime.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest.compiled
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestMode
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import org.junit.runners.JUnit4
-@RunWith(Parameterized::class)
-class WrongStartDestinationTypeDetectorTest(private val testFile: TestFile) : LintDetectorTest() {
-
- private companion object {
- @JvmStatic @Parameterized.Parameters public fun data() = listOf(SOURCECODE, BYTECODE)
- }
+@RunWith(JUnit4::class)
+class WrongStartDestinationTypeDetectorTest : LintDetectorTest() {
@Test
fun testEmptyConstructorNoError() {
@@ -42,7 +35,10 @@
"""
package com.example
- import androidx.navigation.*
+ import androidx.navigation.NavController
+ import androidx.navigation.TestNavHost
+ import androidx.navigation.createGraph
+ import androidx.test.*
fun createGraph() {
val navController = NavController()
@@ -56,7 +52,8 @@
"""
)
.indented(),
- testFile,
+ *NAVIGATION_STUBS,
+ TEST_CODE
)
.skipTestModes(TestMode.FULLY_QUALIFIED)
.run()
@@ -71,7 +68,9 @@
"""
package com.example
- import androidx.navigation.*
+ import androidx.navigation.NavController
+ import androidx.navigation.createGraph
+ import androidx.test.*
fun createGraph() {
val navController = NavController()
@@ -93,7 +92,8 @@
"""
)
.indented(),
- testFile,
+ *NAVIGATION_STUBS,
+ TEST_CODE
)
.run()
.expectClean()
@@ -107,7 +107,9 @@
"""
package com.example
- import androidx.navigation.*
+ import androidx.navigation.NavController
+ import androidx.navigation.createGraph
+ import androidx.test.*
fun createGraph() {
val navController = NavController()
@@ -124,7 +126,8 @@
"""
)
.indented(),
- testFile,
+ *NAVIGATION_STUBS,
+ TEST_CODE
)
.run()
.expectClean()
@@ -138,7 +141,9 @@
"""
package com.example
- import androidx.navigation.*
+ import androidx.navigation.NavController
+ import androidx.navigation.createGraph
+ import androidx.test.*
fun createGraph() {
val navController = NavController()
@@ -160,84 +165,85 @@
"""
)
.indented(),
- testFile,
+ *NAVIGATION_STUBS,
+ TEST_CODE
)
.run()
.expect(
"""
-src/com/example/test.kt:7: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:9: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor TestClass(...)?
If the class TestClass does not contain arguments,
you can also pass in its KClass reference TestClass::class [WrongStartDestinationType]
navController.createGraph(startDestination = TestClass) {}
~~~~~~~~~
-src/com/example/test.kt:8: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:10: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor TestClassWithArg(...)?
If the class TestClassWithArg does not contain arguments,
you can also pass in its KClass reference TestClassWithArg::class [WrongStartDestinationType]
navController.createGraph(startDestination = TestClassWithArg) {}
~~~~~~~~~~~~~~~~
-src/com/example/test.kt:9: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:11: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor InnerClass(...)?
If the class InnerClass does not contain arguments,
you can also pass in its KClass reference InnerClass::class [WrongStartDestinationType]
navController.createGraph(startDestination = Outer.InnerClass) {}
~~~~~~~~~~~~~~~~
-src/com/example/test.kt:10: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:12: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor InterfaceChildClass(...)?
If the class InterfaceChildClass does not contain arguments,
you can also pass in its KClass reference InterfaceChildClass::class [WrongStartDestinationType]
navController.createGraph(startDestination = InterfaceChildClass) {}
~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:11: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:13: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor AbstractChildClass(...)?
If the class AbstractChildClass does not contain arguments,
you can also pass in its KClass reference AbstractChildClass::class [WrongStartDestinationType]
navController.createGraph(startDestination = AbstractChildClass) {}
~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:12: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:14: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor TestInterface(...)?
If the class TestInterface does not contain arguments,
you can also pass in its KClass reference TestInterface::class [WrongStartDestinationType]
navController.createGraph(startDestination = TestInterface)
~~~~~~~~~~~~~
-src/com/example/test.kt:13: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:15: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor TestAbstract(...)?
If the class TestAbstract does not contain arguments,
you can also pass in its KClass reference TestAbstract::class [WrongStartDestinationType]
navController.createGraph(startDestination = TestAbstract)
~~~~~~~~~~~~
-src/com/example/test.kt:15: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:17: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
navController.createGraph(startDestination = TestClassComp)
~~~~~~~~~~~~~
-src/com/example/test.kt:16: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:18: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
navController.createGraph(startDestination = TestClassWithArgComp)
~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:17: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:19: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
navController.createGraph(startDestination = OuterComp.InnerClassComp)
~~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:18: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:20: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
navController.createGraph(startDestination = InterfaceChildClassComp)
~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:19: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:21: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
navController.createGraph(startDestination = AbstractChildClassComp)
~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:20: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:22: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
@@ -256,7 +262,9 @@
"""
package com.example
- import androidx.navigation.*
+ import androidx.navigation.TestNavHost
+ import androidx.navigation.createGraph
+ import androidx.test.*
fun createGraph() {
val navHost = TestNavHost()
@@ -278,7 +286,8 @@
"""
)
.indented(),
- testFile,
+ *NAVIGATION_STUBS,
+ TEST_CODE
)
.run()
.expectClean()
@@ -292,7 +301,9 @@
"""
package com.example
- import androidx.navigation.*
+ import androidx.navigation.TestNavHost
+ import androidx.navigation.createGraph
+ import androidx.test.*
fun createGraph() {
val navHost = TestNavHost()
@@ -310,7 +321,8 @@
"""
)
.indented(),
- testFile,
+ *NAVIGATION_STUBS,
+ TEST_CODE
)
.run()
.expectClean()
@@ -324,7 +336,9 @@
"""
package com.example
- import androidx.navigation.*
+ import androidx.navigation.TestNavHost
+ import androidx.navigation.createGraph
+ import androidx.test.*
fun createGraph() {
val navHost = TestNavHost()
@@ -345,84 +359,85 @@
"""
)
.indented(),
- testFile,
+ *NAVIGATION_STUBS,
+ TEST_CODE
)
.run()
.expect(
"""
-src/com/example/test.kt:7: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:9: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor TestClass(...)?
If the class TestClass does not contain arguments,
you can also pass in its KClass reference TestClass::class [WrongStartDestinationType]
navHost.createGraph(startDestination = TestClass) {}
~~~~~~~~~
-src/com/example/test.kt:8: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:10: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor TestClassWithArg(...)?
If the class TestClassWithArg does not contain arguments,
you can also pass in its KClass reference TestClassWithArg::class [WrongStartDestinationType]
navHost.createGraph(startDestination = TestClassWithArg) {}
~~~~~~~~~~~~~~~~
-src/com/example/test.kt:9: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:11: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor InnerClass(...)?
If the class InnerClass does not contain arguments,
you can also pass in its KClass reference InnerClass::class [WrongStartDestinationType]
navHost.createGraph(startDestination = Outer.InnerClass) {}
~~~~~~~~~~~~~~~~
-src/com/example/test.kt:10: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:12: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor InterfaceChildClass(...)?
If the class InterfaceChildClass does not contain arguments,
you can also pass in its KClass reference InterfaceChildClass::class [WrongStartDestinationType]
navHost.createGraph(startDestination = InterfaceChildClass) {}
~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:11: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:13: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor AbstractChildClass(...)?
If the class AbstractChildClass does not contain arguments,
you can also pass in its KClass reference AbstractChildClass::class [WrongStartDestinationType]
navHost.createGraph(startDestination = AbstractChildClass) {}
~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:12: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:14: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor TestInterface(...)?
If the class TestInterface does not contain arguments,
you can also pass in its KClass reference TestInterface::class [WrongStartDestinationType]
navHost.createGraph(startDestination = TestInterface)
~~~~~~~~~~~~~
-src/com/example/test.kt:13: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:15: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor TestAbstract(...)?
If the class TestAbstract does not contain arguments,
you can also pass in its KClass reference TestAbstract::class [WrongStartDestinationType]
navHost.createGraph(startDestination = TestAbstract)
~~~~~~~~~~~~
-src/com/example/test.kt:14: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:16: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
navHost.createGraph(startDestination = TestClassComp) {}
~~~~~~~~~~~~~
-src/com/example/test.kt:15: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:17: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
navHost.createGraph(startDestination = TestClassWithArgComp) {}
~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:16: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:18: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
navHost.createGraph(startDestination = OuterComp.InnerClassComp) {}
~~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:17: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:19: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
navHost.createGraph(startDestination = InterfaceChildClassComp) {}
~~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:18: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:20: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
navHost.createGraph(startDestination = AbstractChildClassComp) {}
~~~~~~~~~~~~~~~~~~~~~~
-src/com/example/test.kt:19: Error: StartDestination should not be a simple class name reference.
+src/com/example/test.kt:21: Error: StartDestination should not be a simple class name reference.
Did you mean to call its constructor Companion(...)?
If the class Companion does not contain arguments,
you can also pass in its KClass reference Companion::class [WrongStartDestinationType]
@@ -438,600 +453,3 @@
override fun getIssues(): MutableList<Issue> =
mutableListOf(WrongStartDestinationTypeDetector.WrongStartDestinationType)
}
-
-private val SOURCECODE =
- kotlin(
- """
-package androidx.navigation
-
-import kotlin.reflect.KClass
-import kotlin.reflect.KType
-
-public open class NavDestination
-
-// NavGraph
-public open class NavGraph: NavDestination() {
- public fun <T : Any> setStartDestination(startDestRoute: T) {}
-}
-
-// NavController
-public open class NavController
-
-public inline fun NavController.createGraph(
- startDestination: Any,
- route: KClass<*>? = null,
-): NavGraph { return NavGraph() }
-
-// NavHost
-public interface NavHost
-public class TestNavHost: NavHost
-
-public inline fun NavHost.createGraph(
- startDestination: Any,
- route: KClass<*>? = null,
-): NavGraph { return NavGraph() }
-""" +
- TEST_CLASS
- )
- .indented()
-
-// Stub
-private val BYTECODE =
- compiled(
- "libs/StartDestinationLint.jar",
- SOURCECODE,
- 0x8e62b385,
- """
- META-INF/main.kotlin_module:
- H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgUucSTsxLKcrPTKnQy0ssy0xPLMnM
- zxMS8Essc0ktLsnMA/O9S7hEubiT83P1UisScwtyUoXYQoCy3iVKDFoMAGXO
- +shYAAAA
- """,
- """
- androidx/navigation/AbstractChildClass.class:
- H4sIAAAAAAAA/41QTW8SURQ9780wwBRkwC9K/ajVNpSF0MaN0TRSjAkJdtE2
- LGD1YCb0hWEmmfcgXfJbXLsx0Zi4MMSlP8p431CNCxYu3rn33Hdy7sfPX9++
- A3iBfYYDEflJLP3rZiQWciK0jKNme6R0Isa6cyVDvxMKpbJgDLubtJeB0n/0
- WVgMzmsZSX3CYNcHh30Gq37YLyCDrAsbOeIimTCwQQEutvLgKJBUX0nFUO/9
- 3zSvqMsk0G1jRPYDhnJvGutQRs33gRa+0IIkfLawaE1mIG8A1HZK9WtpWIsy
- /4jhdLWsuLzK07dautzbcnnOqq6Wx7zFTosVx+M13rJ+fHC4Z5+X/7IcqWt2
- LuM5xumYYW/j+P8eiKaiIcpnYvGWqjJKFc+nmi7Qif2AodSTUXA2n42C5FKM
- QqpUevFYhH2RSMNviu5FPE/GwTtpyPb5PNJyFvSlkvTbjqJYp8YKR3ReO128
- Yq5NGac8A4dwl9gJcU7RbXxFvrHzBcVPqeYJodEAL7FHeG+twi2UzB0pM260
- CTx6a6+mOS/FTOMzih832hTWghsbjqcpPsYzim/SITO4PYTVxZ0u7nap7X1K
- Ue1iG7UhmMIOHgyRVSgpPFRwFR4pOAqeQvk3MHA2w9YCAAA=
- """,
- """
- androidx/navigation/AbstractChildClassComp$Companion.class:
- H4sIAAAAAAAA/51Sy24TMRQ99qR5DAHSlkfC+xGkthKdpqrYFCGVVEiR0iIB
- yqYL5MyY1smMB42dqMuu+BD+oCskFijqko9CXDsBtsDm+t5z7rnXczzff3z9
- BmAHTxh2hE6KXCWnkRZTdSysynW0NzS2ELHtnqg06abCmG6efWy7IDQ1VMAY
- GiMxFVEq9HH0ejiSsa0gYCg/V1rZFwzB2vqgjiWUQ5RQYSjZE2UYnvX/Z+Eu
- Q2etP85tqnQ0mmaR0lYWWqTRvvwgJqnt5pomTGKbFweiGMtid30QgrvFq+34
- D/k+8yzD5r9NY1j+JTiQViTCCsJ4Ng3ISOZCzQUwsDHhp8pVW5QlHYb27CwM
- eZOHvEHZ7Kx68Slozs62+RZ7Wanyi89l3uCud5u5CRt/71AFtxlqv22iWx6K
- 6b40Vmkv2xxbcr6bJ5Lhal9peTjJhrJ4J4YpISv9PBbpQBTK1Quw3tNaFn6D
- pPcK3+aTIpavlONabybaqkwOlFHUvKd1bv0egw6ZXXIO0Mnds9OH3KcqcpbQ
- ubTxBdVzTz+gWPZgHw8p1ucNqCEEGoyySwvxUzr5Qlw/9/Y6wY05OBf47DKu
- EBfgEVWhF93BXbTw2C+8h7b/3ckD6m0cIehhuYeVHlZxjVJc79HMm0dgBk20
- iDcIDW4ZlH8CofOnlCsDAAA=
- """,
- """
- androidx/navigation/AbstractChildClassComp.class:
- H4sIAAAAAAAA/5VSzU8TQRT/zW4/lyJtRSzgBwpiqcgWQjwIIcESTZPSA5Im
- wmnarmXodtbsTBuO/C2evRA1JJoY4tE/yvhmWyFRDnqYefPe/N7vff74+eUb
- gHWsMZS4bIeBaJ+4kg9Eh2sRSHe7qXTIW7pyJPx2xedKVYLeuyQYw8J1+H1P
- 6UufCGkzJDaFFHqLIVY8WGow2MWlRgZxJB3EkCKdhx0GdpCBg7E0LGQIqo+E
- Yliu/XtWGxSp4+ltQ0YhDhhSmy1/FHr933kWzMUlAZK4ybBarHUDTTzu8aDn
- Cqm9UHLf3fHe8r5PRUri6Ld0EO7ysOuFG8PabjmYxBRD+pKM4dl/FHOVxEYG
- BUybtswwzNeCsOMee7oZciGVy6UMdMSj3Hqg633fpzbkfme862ne5pqTzeoN
- bBo1M1faXKCWd8l+IoxWpld7leHVxWnesQpWdC5OHSs75lipWOHidC65ZpXZ
- c5Z8MZ5PZK0Zq2x/f5+wsrG93KWWIpeZWCqeTRg6WqrFa0v+c0soPcomV+eD
- HfoRMkKtdDXD7F5fatHzqnIglGj63vZVwbQklaDtMUzUhPTq/V7TC/c5YRjy
- taDF/QYPhdFHxkxVSi+MOuyRs/M66Ict76Uwf9OjOI2/omCVOh+jDlmYNoOg
- RJ+QliB5h2Te7CxJm/R4ZF0mbYvQFkmndI50afYzxs8ihqcjT6CGFbqnhijc
- wISZCL0MG7UCWTpDLtcMimS89AnjH66lyQwBI5oUJZUcORcQjRqZr5h8w85x
- +yNmzyKLTcQmIKM9taLCyhF3iQoGKmS/S4z3DmFXcb+KuSoe4CE9MV/FAh4d
- giks4vEhUgoTCkUFR2FJIaGQVcgpFH4BxvFHnF0EAAA=
- """,
- """
- androidx/navigation/AbstractChildObject.class:
- H4sIAAAAAAAA/41Sy2oUQRQ9VfPq6Yzm4SMzxkdMhBgXdhJcaRDGUaGhbcEM
- A5JV9YOkMj3V0F0zZDkrP8Q/CC4CCjLozo8Sb5XjA8zCbvreuueeOtX3UN++
- f/wM4BHuMWwJlRS5TE49JSbySGiZK68blboQse4dyyx5HZ2ksW6AMaxfRO6n
- pf61oYEKQ31fKqmfMlTubw9aqKHuoooGQ1Ufy5JhO/jPM58wOPtxZtVccCPh
- +OFBvxv2XrRwCW6TwMsMm0FeHHknqY4KIVXpCaVybVVLL8x1OM4ykloOhrkm
- Me9VqkUitCCMjyYVcoKZ0DQBDGxI+Kk01Q6tkl06YDZ1Xd7m9ptNna/veHs2
- 3eM77FnD4V/e1/kSN9Q9ho0Lh/vbI/MroZg8J0gq23441Axrb8ZKy1Hqq4ks
- ZZSl3T9TkHW9PEkZFgOp0nA8itKiL4jDsBLkscgGopCmnoPuQT4u4vSlNEVn
- Ljz4Rxa75F/VDt0xdlK+TVWd8hJlTm/NVneo8ow1lGsPzuGc2fb6nAw8xl2K
- rZ8ENEkKcLDwe/Mqsc2z8An87TlaH7B4ZgGODRtvYdPeSPKGBFYOUfFxxcdV
- H9dwnZZY9dFG5xCsxA2sUb+EW+JmifoP1gZuws4CAAA=
- """,
- """
- androidx/navigation/AbstractChildObjectComp.class:
- H4sIAAAAAAAA/5VSW2sTQRg9M0k2m220tV6aWO8t4gXdtvhmEWJUWNiu0IaA
- 9Gk2u7TTbGZldxL6mCd/iP+g+FBQkKBv/ijxmzFU0L64y36XM+c7s3OYHz8/
- fwXwDOsMj4VKilwmx74SE3kgtMyV34lLXYiB7h7KLHkbH6VU5qP3dTCG9fMG
- emmpz4Yss8LgbEsl9QuGyoOH/SZqcDxUUWeo6kNZMjwJ/2Pv5wzu9iCzih64
- kXGDaK/Xibqvm7gAr0HgRYa1MC8O/KNUx4WQqvSFUrm2yqUf5ToaZxlJXQqH
- uSYxfyfVIhFaEMZHkwq5wkxomAAGNiT8WJpug6pkkzaYTT2Pt7j9ZlP3+wfe
- mk23+AZ7WXf5t48OX+KGusVw/9wD/u2V+Z1ITF4RLJWlPB1qhtXdsdJylAZq
- IksZZ2nnz0nIwm6epAyLoVRpNB7FadETxGFYDvOByPqikKafg95ePi4G6Rtp
- mvZcuP+PLDbJw6o9eNtYSvkWdQ7lJcqc3prtblPnG3so1x6dwj2xy3fmZGAH
- dyk2fxPQICnAxcLZ8AqxzbPwBfzdKZqfsHhiAY57Nt7Emr2h5A0JLO+jEuBy
- gCsBruIalVgJ0EJ7H6zEdazSegmvxI0Szi+zsivd3gIAAA==
- """,
- """
- androidx/navigation/InterfaceChildClass.class:
- H4sIAAAAAAAA/41Qz28SQRT+ZhZY2FJZqFZK/VWrtnBwaaMnTWNbY0KCbVIb
- DnAaYKVTltlkZyA98rd49mKiMfFgiEf/KOMbSpqYcPAw773vvW++9+P3nx8/
- AbzADsOOUP0klv2rQImJHAgjYxU0lAmTj6IXHl/IqH8cCa1dMAb/UkxEEAk1
- CE67l2HPuHAYtpZJnIfa3Mi4SDNkXkslzQFDarddbTE4u9VWHi5yHlLwCItk
- wMDaeeSxmgPHLaKaC6kZqs3/nPIVtRmE5tAqkX6bodgcxiaSKngfGtEXRhCF
- jyYO7c+syVkD6juk/JW0qE5Rf4/haDYtebzM52829bi/4vGsU55N93mdHa2W
- Mj6v8Lrz61OG+6mz4g3KEruSyqb9jFXaZ9heOv8/J6KxaIriiZi8pbRUc8rz
- oaEbHMf9kKHQlCo8GY+6YXIuuhFlSs24J6KWSKTFi6T3IR4nvfCdtGDjbKyM
- HIUtqSVVD5WKzVxYY48OnKKOGXole3FanFPsIkv2MaEDwpy8V/uOldrmNxS+
- zDnbZO0v4CWekF2/ZsFH0Z6SIqtGu5Du2kIrsBcmn659ReHzUpn8NWEhw/F0
- brfwjPwbqt2m2p0OnAbWG7jbQBkbFKLSwCbudcA07uNBB65GUeOhRl7jkUZW
- o6Sx9hcxw6Dy8gIAAA==
- """,
- """
- androidx/navigation/InterfaceChildClassComp$Companion.class:
- H4sIAAAAAAAA/51STW/TQBB9u07zYQJNWz4SvgtBakHUTQXiUIQEqZAspUUC
- lEsPaGNv203sNfJuoh5z4ofwD3pC4oCiHvlRiFknwLlcZmfemzezfuufv77/
- APAMjxieCx3nmYpPAy0m6lhYlekg1FbmRyKS3ROVxN1EGNPN0s9tF4SmjgoY
- Q2MoJiJIhD4O3g2GMrIVeAzll0or+4rB29js17GEso8SKgwle6IMw4vef23c
- Zehs9EaZTZQOhpM0UE6hRRLsySMxTmw308bm48hm+b7IRzLf3ez74G7zWjv6
- R35KC5Zh62LTGFb+CPalFbGwgjCeTjyykrlQcwEMbET4qXLVNmVxh6E9m/o+
- b3KfNyibTavnX7zmbLrDt9mbSpWffy3zBne9O8xNeHIBiyq4xVD76xNd80BM
- 9qSxShe6rZEl77tZLBmWe0rLg3E6kPlHMUgIWe1lkUj6IleuXoD1UGuZFxsk
- vZj/IRvnkXyrHNd6P9ZWpbKvjKLm11pntthj0CG3S84COrl7ePqSe1QFzhM6
- lx5/Q/WsoO9TLBdgiHWK9XkDavCBBqPs0kL8lE6+ENfPCn+d4PocnAuK7DKu
- EOfhAVV+IbqNO2jhYbHwLtrFH08eUG/jEF6IlRCrIdZwlVJcC2nmjUMwgyZa
- xBv4BjcNyr8BSdfj0S4DAAA=
- """,
- """
- androidx/navigation/InterfaceChildClassComp.class:
- H4sIAAAAAAAA/5VSW08TURD+zrb0RpG2KJbiBQS1FGELYkyEkGCNZpNSEyRN
- hKfT9lBOuz1rdk8bHvktPvtC1JBoYoiP/ijjnKXCgySGh52Zb3bmm8uZX7+/
- /QCwhjWGRa5avidbR7biA9nmWnrKdpQW/gFvisqhdFsVlwdBxet9iIMxZDp8
- wG2Xq7b9ttERTR1HhGH2KppdEegLqjhGGGIbUkm9yRAt7i3UGSLFhXoacSRT
- iCJFmPttBraXRhpjSVi4QaH6UAYMS9VrdLpOpdpCbxk2qrHHkNhousPaz65B
- NG8EVxQRxy2GlWK162kisjuDni1NjuKu/Uoc8L6rK54KtN9vas/f5n5X+Ovn
- 091OYRJ5huQFGcPz64xz2cV6GgVMm83cYZiren7b7gjd8LlUgc2V8nRIFNg1
- T9f6rkuLyP5teVto3uKak8/qDSJ0AcyIpBGgrXfJfyQNKpPVWmF4c3acS1l5
- K/zOjlNWZjRlJaL5s+OZ+KpVZi9Y/OVYLpaxClY58vNjzMpEd7IXKEEphWhi
- JBMzdKum3/9eCfVGrWRrfPCK3FKFIctdzTC901da9oSjBjKQDVdsXU5LR1Lx
- WoJhvCqVqPV7DeHvcophyFW9Jnfr3JcGD51pRynhh+sVlJx65/X9pngtzb+p
- YZ36P1WwQmuPUnsx0lPmHcheonXFSN8jnTNXSzpCOI4EyWVCmxRtkU6VTjFa
- mv6K8RNCFuxhJuCgTHLyPAoZZM2DkGXYaBnEOzHkss07kR4pfcH4pytp0ucB
- Q5oEbiI5TM4jfGmkv2PyPTvF1GfcPQk9ERrNFGRhEwUabjXkfoKnpCvkv0+M
- M/uIOJh18MDBHObJxEMHj/B4HyxAEQv7SATIBigFSAdYDAzMBZgIUPgDTG80
- x3MEAAA=
- """,
- """
- androidx/navigation/InterfaceChildObject.class:
- H4sIAAAAAAAA/41SS2/TQBD+dpMmjmtoWl4J5VXaotIDbivEhQqpBJAsBSPR
- KBLqaRMv6SbOWrI3Vo858UP4BxWHSiChCG78KMSsCeUAEtjaeXwz83lm1t++
- f/wM4CHuMWwJHaWJik58LXI1EEYl2g+0kelb0ZetYxVHr3pD2TdVMIb6UOTC
- j4Ue+L/QEsPa3zg6MjPnPFUsMFT2lVbmCUNp637XQxWOizJqDGVzrDKG7fb/
- 9vKYwdnvxwWdC245nCA87ByEreceluDVCKwzrLeTdOAPpemlQunMF1onpqDN
- /DAx4SSOiWq5PUoMkfkvpRGRMIIwPs5LtCJmRc0KMLAR4SfKejtkRbv0gdnU
- dXmDF2c2db6+443ZdI/vsKdVh395X+F1blP3bC//3JLtJRT5M8KULuIPRoZh
- 9fVEGzWWgc5VpnqxPPg9Bi2vlUSSYamttAwn455MO4JyGFbaSV/EXZEq689B
- 9zCZpH35QlmnOSfu/kGLXVpgmWau0GnajZK+Q4Nbf4U0p5cukLw18ny7HdIL
- 22dwT4vw3Xky8AjrJL2fCVgkC1R44bz4GmXbZ/ET+JszXPyA5dMC4Ngo5G1s
- Fn8rwyUiuHyEUoArAa4GVNogE80A17F6BJbhBm5SPIOX4VYG5wfApo4N6gIA
- AA==
- """,
- """
- androidx/navigation/NavController.class:
- H4sIAAAAAAAA/4VRu0oDQRQ9d2I2ukZNfMYXGGzUwlWxUwSNCIGooJLGapId
- 4pjNDOxOgmW+xT+wEiwkWPpR4t01vc3hPGbuHO58/3x8AjjGJqEqTRhbHb4E
- Rg50RzptTXAjBzVrXGyjSMUFEKH0LAcyiKTpBLetZ9V2BeQI3qk22p0Rcju7
- zSLy8HxMoECYcE86IWw3/p1+Qig3utZF2gTXyslQOsme6A1yXJFSmEoBBOqy
- /6JTdcAsPCRsjYa+LyrCFyVmo+HkcmU0PBIHdJH/evVESaTnjii9XeZnL1Xi
- tMla7Hcd16zZUBHmGtqom36vpeIH2YrYmW/YtoyaMtapHpv+ve3HbXWlU7F6
- 1zdO91RTJ5rTc2OsywYnqELwFsad06UwVlgFmQbye++YfGMisMroZeYs1hiL
- fwcwBT/L1zNcwUb2XYRpzoqPyNUxU8dsHXMoMUW5jnksPIISLGKJ8wR+guUE
- 3i+86bUs6wEAAA==
- """,
- """
- androidx/navigation/NavDestination.class:
- H4sIAAAAAAAA/4VRO08CQRD+ZoEDTpSHiuAjUWOhFh4SO42Jj5iQICZqaKwW
- 7oLLYy/hFkLJb/EfWJlYGGLpjzLOnTRWNl++x+zMZPbr+/0DwAm2CLtSu0Nf
- uRNHy7HqSKN87TTk+NoLjNKRTIIIua4cS6cvdce5a3W9tkkiRrDOlFbmnBDb
- P2hmkIBlI44kIW6eVUDYq//f/pSQr/d801faufWMdKWR7InBOMZLUgjpEECg
- HvsTFaoKM/eYsD2b2rYoCVvkmM2mqWJpNq2KCl0mPl8skRNhXZXC1/m/c496
- hve88l2PkK0r7TVGg5Y3fJStPjuFut+W/aYcqlDPTfvBHw3b3o0KRfl+pI0a
- eE0VKE4vtPZN1DjADgSfYb5zeBXGEisn0kDi8A2pVyYCZUYrMi2sM2Z+C5CG
- HeUbEa5hM/owwgJnmSfEalisYamGLHJMka+hgOUnUIAVrHIewA5QDGD9AKYj
- 0APtAQAA
- """,
- """
- androidx/navigation/NavDestinationKt.class:
- H4sIAAAAAAAA/61WbVPbRhB+zsbYCGOECW8GDAGHGGgQEJI0hZJSaILKS1Kg
- pJS26WGEEQgpo5OZdDrT5lP/Q7/2FyTlQzplppPJt/ZHdbqnCgw2BibDB5/2
- 9naffXZv787//PvnXwDGsMGQ4fam65ibLzSb75t57pmOrS3y/RlDeKbtT+e8
- KBiDusP3uWZxO6893tgxcqQNM9TmXIN7xiOXP99msLLzFfCmHdtzHcsy3PH5
- UqDx+V3Hs0xbc40ti+ba3LTFhRjvrwTmRxtncK4y3MTA5MURe+cdN6/tGN6G
- y01baNy2Hc+3Etqi4y0WLIusMudZkQnfsAwyq57wtk0xGYPCkA447ezvaabt
- Ga7NLU2nJMjfzIko4gxNuW0jtxuEecJdvmeQIcPN7Bk5FjXLEiQ/3r8aRwL1
- Cuqg0m4Kj7veiV2OIcnQcV76UVyTnE3b9CYZwlkJ2IwWBU1oJcCMmdnKnOoG
- pjM0ZGSOp/W9l9g1hmR5UgwR1yl4BkNLhZZhaDwRKrNpbPGC5TH8fKWNqZdb
- Xtg5HWWFeFYYHTsmuFWR4KwjvCs8M+bVBLrUaek8NxTDD1eU9PvsR2Pe8Hxv
- 3aajYOeMJWOL4Xr2bMcVOihHHabmyty6LnCKYwCDNQjhA4ZUaeCnprc95eZ9
- oOxF8QNjotGSqwTSdzmIOIYxIkmNMrQSKd22Dbe8JJUoPaaD6GaKTkSpyTwb
- 4mxCZQBx3MU9SehDhthEzgpums5zs4liXMGEvIHOfMpKs46CEKuy+v+X1ycK
- HmCqgmspvyimFcxI84ajPlwwPL7JPU65h/b2w/SoMjnUyAF0Ae5KIUSLL0wp
- DZO0OcLw99uXo8rbl0qoNaSEYuGzv/STJioNR6atodQNNZ4KDdcNhIYTo9Vq
- PcnqaCIWUhtSsSS5trLh5Oy7X2In7BrPs+tpIHxaZO9+q45RnFQV2YRJW0XK
- SFFZrUZJGSNlTVGpqLUyIWqf9EWnjYrRVrHGUawwKMVCU4FP/wMZ2qUbsn2p
- YHvmnqHb+6Yw6QWdKr6qtKXTzia9C/Xzpm0sFvY2DHdFvrLyGXFy3Frlrinn
- gbJm2cwTdMElOVOKe/yyngpQt+zx3O4Cfx5AxIt8DVpWlp2CmzMemnKtLYBc
- LSOKEWrvKmqFMFLyTqC6fEWzavrG6JuSR7JMR6eiRBdDGyI0q8Iazb4LMJsH
- krV/oGEw2UhjePIQTWtv0PZKtiC+DrwTaMQ6yQPkkSCcFNohW7MZHeiUXUtS
- EmmylFIXusn3Gx8heorBt/RrDAeTo7EGUGtwHT0kS2I/kluEvunOyE+/IsIW
- KhIMUxJyZDGfadLPR6VoSYJWiUmRdfMJ1mn0BqzTx6zTAWtZocx7VaijYoX6
- zq3QjctX6ObVVKiNorX73dB8gnVphfoqViiLfvpKIh3+ClD1O2698qke1UPa
- n8yrDUPQyr1ul3p1l3iN4U651/1Sr56SPv8ILUGppv18gN5DTFBRPj7ArUM8
- WFPr3+DTA9w+xIwvf3aA+6+PQRNBERSi00zgYTyjuUKrM/gSq0Tre3/rnoLT
- N0/6h7Qfj9YR1jGrQ9fxOeZ0zGNBxyIer4MJPMEX67gmMCAwKJAV6BcYppMt
- MCSgCdwVuCcwJnBHICKwJNApkBRYFugS6Bbo+w//HMZ8gQ0AAA==
- """,
- """
- androidx/navigation/NavGraph.class:
- H4sIAAAAAAAA/31SXU8TQRQ9s6XbbUFaQBAq+AEoBZStxCdLSPwIWlOr0qYv
- PE3bSZl+zJqdacNjf4v/wCeND6bx0R9lvLOtCkHcZO6de+65d87OnR8/v34D
- 8Bh7DKtcNcNANs98xQeyxY0MlF/mg5ch/3CaAGNYv4LxQmgjVRQmEGNwD6SS
- 5pAhltuuzSAON4UpJBimzKnUDLdK/zuqwLCghakYHppznRkWc6U2H3C/y1XL
- f1tvi4YpbNdI+EH1yeXMYa5ajdIbpSBs+W1h6iGXSvtcqcBELbVfDky53+3S
- kbP693nHQd8ID2nS2QlMVyq/Pej5UhkRKt71i8qE1EY2dAJzJKpxKhqdSZ93
- POQ9QUSGrX+IPYdUbJNWwV7PAq6nMI9FhvnLJQxzpYmKN8LwJjecMKc3iNHY
- mDVJa8DAOoSfSRvladd8xPB+NMymnGVnvDxaGSc1GpKzxnO8peXRcN/Js2fx
- 7x9dSr5ey8SyTn5q3fNGw0x8x8m7+24mkXVejQmebbzPsHnVAM/Ni2RaVVX6
- g4uJvY6hl/A8aAqGdEkqUe736iKs8npX2DsIGrxb46G08QRMVmSLivsh7TeP
- +8rIniiqgdSS0n8u/enfwTKkKkE/bIgjaetXJjW1ccU5Iu7CobdpP4fk0lMl
- u0WRb8WTj+98hvcpSufIuhGYxDbZmTGBohT5OUwTEouKC8R2yCd25zNfsHSx
- 3CW6LV8aUybldpfGDcrvROxr2LWYFTEbAQ8iex8PyR8Rukwnr5wgVkS2iJtF
- rGKNtrhVxG3cOQGzv7Z+gqRGSmNDw9WY1tjUuBfZtMbML5qRw8f+AwAA
- """,
- """
- androidx/navigation/NavHost.class:
- H4sIAAAAAAAA/31OTUvDQBB9s9F+xK9ELVTEv2Da4s2TUMRAVVDwktO2Wcs2
- 6S50t6HH/i4P0nN/lDiJd2fgzZt5w5vZ/3x9A7hDj3AtTb6yOt8kRlZ6Lr22
- JnmR1ZN1vg0iRAtZyaSUZp68ThdqxtOAEE8K60ttkmflZS69vCeIZRWwLdXQ
- rQEEKni+0XU3YJYPCb3dthOKvghFxOyzv9uOxIBqcUS4mfzzD99gy5i7sXJe
- m0a8LTwhfLfr1Uw96lIRrt7Wxuul+tBOT0v1YIz1zapr8RUc4C8ELho8xyXX
- ITsfcrYyBCnaKTopugiZ4ijFMU4ykMMpzjIIh8gh/gUZbPE0RgEAAA==
- """,
- """
- androidx/navigation/Outer$InnerClass.class:
- H4sIAAAAAAAA/41U30/bVhT+rp0fjgngAG35kbXdyFgS2jqwdusK7QZ0DLMQ
- OpjQOvZySbxgCDazHdS9TDz1T6i0vUyapj3x0EobTKtUsfZtf9M07VzbTbrQ
- IST7nnOPz/nOd88513/988czANexypDjds11rNoD3eZ7Vp37lmPry03fdHOG
- bZvuXIN7XhKMQdvie1xvcLuuL29smVU/CZkhMW3Zln+HIZY3CmsMcr6wlkYc
- SRUxKAyKJVBm3DoDM9JQ0ZWChDT5+5uWxzBWPguBKYauuukbLSxKYzCoVWdn
- 17FN258gwKqz+y1DgXicFXO07Lh1fcv0N1xu2Z7ObdvxA29Przh+pdloTInD
- JFTifJ4hLVLkaubXvNnwGTbyZ0tkGOXO2k2dkWMa/RgQ2YeplL6z6ruWTccf
- yBdegQytdJ4LnbbZptWomW4SF1VcEu0YaGPnX3bmtoI3qZF8d9e0awxX8yeh
- T2aLkIngKHIC/G2GrCj9aY7vCMe8cJw73bEoHMfTyOINoV2lw29yb3POqZkM
- mXakYftmXZyvFA4gTZiOSRUTeJdOZH7T5A2asXP519T/S5r909pPvecbDZOq
- Gnf8TdNl6DuJQmTK247fsGx9yfR5jfucbNLOnkz3i4klJRbQ8G+T/YEldsRV
- qtHA/ny8f1GVBiVV0o73VXokTVElJUGyi6RMskd5/lAZPN6flEpstrsvoUnD
- Ukl+/lNC0mKLKS0pdgsvHsqL/ZpCOjkqihQ6kZmROUW6OqloXcOxQVZiCy8e
- yRSYDj0eMdK7Se8R+kqmBa8QneGYEtcSguskEycY+t+BTWKe7mJ7sqgqFb53
- 1/R8yw7crm3TbYmF3estW7ZZae5smO7nosCirk6VN9a4a4l9ZBxZadq+tWMa
- 9p7lWWSaaTeHoXvV59XtJb4beec6ve9xl++YxO0/Yek2R5O26qrTdKvmvCUg
- hiKItRPpaJok+pmJGvSJHxhpCun0W6B1kXbz9F0iqRaPkCqO/IbuJ7ST8Cmt
- PRAt1ym+hBTJMu3Oh970rVcMB2kClaoGjd4QUxczQzJe/BXdBy24RGAsBTDp
- 0CGCyRC5l8GjncHstQH0ayFYETBBLAWn1FNI90eOcOFxKygkm2qRTUVklyI2
- 5wAthUEMRbnHomJlsrHvvociGEwXRw4xEkJWaJXBBAJd7ij9LZKCWvYpLt0/
- wuW+tw4xJiIPUdAKh7hyiGuPO46RjRi9woNWvVWDsagGAYPfcb2zDEoUz3AD
- 70U8viIp2pUrjv+CeOxg/E9IPyAuH4wfQ1oSQFfo/VFYYmFPKkH75KTyNzJJ
- 2rcrlmtVLIeb+IDyLJOeFKTeD2pwLwil+4VPsEDl+ywANLBC8guy36JOTa1D
- NjBt4LaBO/iQVHxkYAaz62Ae5nB3Hb2eeD72oAZrwoPmIeOhz0O/hxuB8aYH
- 3UOW9H8BSGQIivsHAAA=
- """,
- """
- androidx/navigation/Outer$InnerObject.class:
- H4sIAAAAAAAA/41US08TURT+7p0+plMe5SFPxQdFXsoUxBXEBFHjkFKMJRhl
- dWlHGGhndOa2YcnKnVsXLl24YiFxQaKJqRI3/ijiudNREKIxac/5zrnnNd+5
- Mz+OP30BMIvbDCPCLfueU941XVF3NoV0PNdcqUnbz1qua/srG9t2SSbBGDLb
- oi7MinA3zV9ejSEx77iOvMOgjY2vtSCOhIEYkgwxueUEDKP5/+owx6BLryh9
- x91k6B4bz590a3opYjjv+Zvmti03fOG4gSlc15NhwcAseLJQq1QoKn2qrI42
- Krwlgq1Fr2yHQ1ra99nj1zS4/bImKjThhbH82SebG3/GkP1XN2olNio2tYt7
- csv2GTrPV6HW86VKyI8BrkjRrUJxdaGweL8FAzBS5Bxk6MjveJLCzGVbirKQ
- ghJ5ta7RjpgSKSXAwHbIv+soK0eoPM3wvLE3ZPA+bvBMY8/gugLpSOuGcmXa
- 9KNXRl9jb4bn2N2kzr+9S/AMX+rKaAM8F5vRM/GBWB/LsYdHb7SlVCZB3iRh
- RlgnnFJYdZthaob+v24ziXF6lIKo37MD6bjhydSOZBh8XHOlU7Utt+4EDpG2
- cEIkXZPmYtrzjmsXatUN219VxCo+vZKorAnfUXbkbC1KUdpZFi8iO3u29iPh
- i6pN4/zRpCW8EosVEQQ2mUbRq/kl+4GjSvRHJdbODYdp2k8spL5frYv0DbIS
- pFtJx+k0Hlo3yTLVgpR34hD6AQGOqSgYFGCSbGkGIEWlVNE0eXiYfDVK1jrb
- P4RHJ+FaFH66M72L6Ij6nqR27v8llaEL3VEnizQn3Tsx+R7x2P7kV/C3iGv7
- kw3wJ7H9cPAcyRh4Ug+L9TQTomIK9dCfETtQV5peIAI6+n5T0RsmAOnP4E8P
- 0f8RFw9Ch4YZkopHjgm0Eau3wn6T9C1SozFcInqG1qFZuGzhikVPd40ghi1k
- MbIOFuA6RtdhBOo3FiARoCsEPQEyIUiT/AmvDPuL4QQAAA==
- """,
- """
- androidx/navigation/Outer.class:
- H4sIAAAAAAAA/4VRW2sTQRT+ZjaXzSbaNF6aWFsvTbWp4rbFp1qEGhUW0hRs
- CUieJskQJ9nMwu4k9DFP/hD/QfGhoCBB3/xR4tltNA9S3GHPN+f2nTnn/Pz1
- 5RuA53jCUBG6Fwaqd+ZqMVF9YVSg3eOxkWEWjKE4EBPh+kL33ePOQHZNFhZD
- 5kBpZV4yWFu1VgFpZBykkGVImQ8qYlhtXMn6gsE+6PpJvgMeJ9le8+T0sFl/
- U8A1ODkyXmfYaARh3x1I0wmF0pErtA5MwhO5zcA0x75PVMuNYWCIzD2SRvSE
- EWTjo4lF3bFY5GIBBjYk+5mKtR269XYZarNpweFl7vDibOpw27J/fOTl2XSP
- 77B9bqVeZW3+/VOGF3mcsMdiGsfTWoZ1X0TUZD5RLqfCUL2y4+oiKYt7DJv/
- ifwz5wfUXlNMXsvIKJ1EPRtSodV3Y23USHp6oiLV8eXhYjK0gHrQkwxLDaVl
- czzqyPBUUAxDqRF0hd8SoYr1ubGweJqkZOckGIdd+VbFvsq8TuufKtilFaWS
- uVbijRFWScsQFgk5nXSibZLmxtMnTG9fwD5P3I/mwcBTPCZZuAxAjqgAG/m/
- ySsUHX/5r+DvL1D4jKXzxGBhi2SJ3PfpX6N3PCRcJ6wlJTawTbhPNMtEXGrD
- 8nDDw00Pt3CbrljxUEalDRbhDlbbSEdwItyNkImwFmH9NzJGDiwjAwAA
- """,
- """
- androidx/navigation/OuterComp$InnerClassComp$Companion.class:
- H4sIAAAAAAAA/51TTW/TQBB9a6dxYkJJUz4SoJRCgBRB3VQIIRUhQapKkdJW
- giqXHtAmWcom9hp511GPOfFD+Ac9IXFAUY/8KMSsE6i4IJXLzJt582a0M/aP
- n9++A3iGBsNzrgZJLAcngeJjecyNjFVwkBqRtOLoU72tFKGQa52F1nBFJR4Y
- Q3nIxzwIuToODnpD0TceXIb8S6mkecXgNta7JSwg7yMHjyFnPkrN8KLzfyO3
- GZqNzig2oVTBcBwFUpFE8TDYER94GppWrLRJ0r6Jkz2ejESyvd714djRy/X+
- Ofk+yliGjYt1Y1j6LdgThg+44ZRzorFLy2TWFK0BAxtR/kTaaJPQoMlQn058
- 36k6vlMmNJ0Uzj671elky9lkb7yCc/Yl75QdW7vFbIcnF9mRh1sMK/9UeFhh
- WPxbxlD8s1x62z4f7whtpMqkGyNDF2vFA8FwpSOV2E+jnkgOeS+kTKUT93nY
- 5Ym08TxZOm8v6M7+uzhN+mJXWq72NlVGRqIrtaTi10rFJpuj0aQT5ezeyDv2
- c6Hn36MosIskv/D4KwqnGX2fbD5L7qJOtjQrQBE+UGaELs3FT8k7c3HpNDuK
- FVyfJWeCDF3GInEuHlBUIfY27mAVtQzdJf8wG7yGR9kPQ7sgTfkIbhtLbVTa
- WMZVgrjWpt43jsA0qqgRr+Fr3NTI/wIu2QAobQMAAA==
- """,
- """
- androidx/navigation/OuterComp$InnerClassComp.class:
- H4sIAAAAAAAA/5VUW08bVxD+zvq2XgysIRcHSJMWNzWGsECTlAJpuJelXFJI
- aQjtw8HemgV7l+6urfSlylN+QqT2pVIf+sRDorZQFamiyVt/U1V1zu5ibhES
- kn3OzOzMN9+ZmXP++e/PvwDcwdcMPdwqOrZZfKpZvGaWuGfalrZY9Qxnwq5s
- Z3XLIqnMXVeoCTAGdZPXuFbmVklbXN80Cl4CEYb4iGmZ3icM0ZzetcIQyXWt
- pBBDQkEUMoNsCqQxp8TA9BQUNCQhIUX+3obpMvTOXYTIMENDyfD0Oial0xmU
- An2zLcPy+gm4YG9/x9BPfC6K3TlnOyVt0/DWHW5arsYty/b8KFdbsL2Fark8
- LA4XV+gMVxhSIlW2aHzDq2WPwcldLKGuz52u6fAFOafQikuCTRuV2rOXPce0
- qCyXcl3HoAMrne/qadt41SwXDSeBdxTcEO3KnMTPHXbvvox3qdl8e9uwigy3
- c2fhz2YM0YlkJ7IiwfsMHaIt5zl+IBxzwnHifMe8cOxOoQPXhXSbCrDB3Y0J
- u2gwpI8idcszSuKMfcGQ0hRqGFDQjw/pRMa3VV6mObyce0svnjBkzxsJmge+
- XjaosjHb2zAchpazKMRrpFAOb8m9i3Q3KxZukUsCw2Ki57Zsj5C0zVpFM+lY
- jsXL2mQwfhPEyHOqBc925rmzRUUKLuJ9BSOgzMk6GMPghYbsiAbVfRRj4gKP
- U4kP2cwbHi9yjxNFqVKL0AvDxJIUC+jab5H9qSk06oBUpCu6c/DspiJlJEVS
- D54p9JNUWZHkOO0NtEdobyKz/Pq5nCHX5gGpjw2x5vHGlrgqtUl9kdc/xyU1
- OptUE0KbefM8MtuqyiQfPBuQZSlwIjMjc5JkZUBWG9qiGdbHZt68iFBgKvB4
- wUhuJLlJyEvpOrxM+duickyNC84DTJzk+rlVS+AhQ9PJ0lGVFnht0nA90/Ld
- e7fonWhfqlqeWTF0q2a6Jg3Q2NFQ0YwGE9w8Z1rGQrWybjiPxJCJ2bILvLzC
- HVPoobFx2eOFrXm+HerZ09gPucMrBnE8kSR1xNMgVVm2q07BmDYFxLUQYuUM
- ObozEj3roPWamASqySPS4rRfpr1FPO+i86THfOsXpE2Tt0S7kt9DMt/+Oxpf
- +QgrtDZBjMUkYU5R1CS+JO1K4E3fmsUAkSRQqZJQ6R9gamKuaI/lf0PjTh0u
- 7hunfJhU4BDCpIncYXDn6WD21gB6WAlWBPQTS8EpuQ9ptX0PV1/WgwKyyTrZ
- ZEj2WFnUJDJUriD3rbCA6Y7o9z9AFgxG8u27aA8gH9MaARMI9KyF6YdoF9Q6
- 9nFjdQ83W97bxS0RuYsutWsXPbvofXnqGB0ho+PtYVS2dJ1HUAOfwR+4c7oM
- chjPcBf3Qh5f0S7alc13/4JYdKf7b0g/IhbZ6T6ANC+Aeuj/k7BEg5489tsX
- Scj/Ip0g/ahi2XrFshjEx5RnleSEIPWRn34IiZBqxk9KxPYxssr28OBXTLzy
- LRE88adOzNfnWKIij5A0Svuan36ZKINkRpMVw9QaIjqmdXyqYwY6iZjV8Rnm
- yMHFPBbWoLpodrHoQvHXuCssaRctLlpd3PWNgy40Fx2+PPo/FBJ91lIJAAA=
- """,
- """
- androidx/navigation/OuterComp$InnerObject.class:
- H4sIAAAAAAAA/41US08TURT+7p0+plMe5SFPxQeoLVWmoK4gJoAah5RihGCU
- 1aUdy0A7gzO3DUtW/gQXLl3ohoXEBYkmpsrOH2U8dxgFIRKT9t7vnHvO+c58
- 5878+Pn5K4C7uMeQE27F95zKjumKplMV0vFcc6khbX/eq2+PWa5r+0vrm3ZZ
- JsEYMpuiKcyacKvmb6/GkJhxXEfeZ9CyudU2xJEwEEOSISY3nIAhX/xvlmkG
- XXrL0nfcKkNvNlc8ZjzyUsRo0fOr5qYt133huIEpXNeTYdHALHmy1KjVKCp9
- oqyODiq8IYKNea9ih41amvuhMEPN268aokZdXsgWTz/ddO4Fw9h5bEQl1ms2
- 0cU9uWH7DN1nqxD1TLkWamSAK2F0q7S8Mluaf9iGIRgpcg4zdBW3PElh5qIt
- RUVIQYm83tRoVkwtKbWAgW2Rf8dRVoFQZZLhZWt3xOAD3OCZ1q7BdQXS0a4b
- ypXp0A9fGwOt3SleYHNJnX9/l+AZvtCT0YZ4ITalZ+JDsQFWYI8P32gLqUyC
- vEnCjLBOOKWwYptiqodL5040iRw9Tkk0H9iBdNzwdGJLMgw/bbjSqduW23QC
- h4SbPRaTrsvRcDqLjmuXGvV1219R4ipNvbKorQrfUXbkbF+Wory1KLYje+x0
- 7SfCF3WbWvqLpC28FvM1EQQ2mcay1/DL9iNHlRiMSqyeaQ6TNKNYKP+gGhnt
- t8hK0N5Oe5xO46F1myxTDUl5xw+g7xPgmIiCgTk6BtqOApCiUqpomjw8TL4a
- JWvdnR/Do+NwLQo/yUzvJLoi3uPU7r1/pDL0oDdismjntPeP598jHtvLfwN/
- i7i2l2+BP4vthY0XaI2BJ/WwWN9RQlRMoT76M1IH6lrTS0RAx8AfKfrDBCD9
- Bfz5AQY/4eJ+6NAwRavSkWMcHaTqnZAvT98l1RpdMZJnZA2ahcsWrlj0dNcI
- YtTCGK6vgQW4gZtrMAL1ywZIBOgJQV+ATAjStP4Cqe39ku0EAAA=
- """,
- """
- androidx/navigation/OuterComp.class:
- H4sIAAAAAAAA/41RW2sTQRT+ZjaXzSa2abw0sbZVW7Wp4rbFp1qEGBUWYgq2
- BCRPk2SJk2xmZWcS+pgnf4j/oPhQUJCgb/4o8ew2WkQo7rDnm3P7zpxzfvz8
- /BXAEzxkWBWqF4Wyd+IqMZF9YWSo3MOx8aN6OHqfBWMoDsREuIFQffewM/C7
- JguLIXMglTTPGKytaquANDIOUsgypMw7qRnWG5cyP2WwD7pBwuGAx4m21zw6
- rjXrLwu4AidHxgWGjUYY9d2BbzqRkEq7QqnQJFzabYamOQ4ColpqDENDZO5r
- 34ieMIJsfDSxqEsWi1wswMCGZD+RsbZDt94uQ3U2LTi8zB1enE0dblv29w+8
- PJvu8R22z63U86zNv33M8CKPE/ZYTLPgKUVtBELruBeGfGI4nw7Do0s73/w7
- OYt1esR/ZPye/R1qtykmL3xtpEoiHw+p6MqbsTJy5HtqIrXsBH7tYlK0lHrY
- 8xkWG1L5zfGo40fHgmIYSo2wK4KWiGSsz42Fiyf6lOwcheOo67+Ssa8yr9P6
- pwp2aWWpZM6VeIOEm6RlCIuEnE460e6R5sbbIExvn8E+Tdz358FADQ9IFs4D
- kCMqwEb+T/IyRcdf/gv42zMUPmHxNDFY2CJZIvdt+lfpHXcJ1wirSYkNbBPu
- E80SEZfasDxc9XDNw3XcoCuWPZRRaYNp3MRKG2kNR+OWRkZjVWPtF1d0yO47
- AwAA
- """,
- """
- androidx/navigation/TestAbstract.class:
- H4sIAAAAAAAA/4VRy0oDMRQ9SduxHau29dX6AHUh6sLR4kJQhKoIA7WCSjeu
- 0s6gsdMMTNList/iH7gSXEhx6UeJN2P3bg7nkdwcbr5/Pj4BHGGdYUOoIIll
- 8OIpMZSPwshYefehNo2ONonomikwhtKzGAovEurRu+k8h9bNMDinUklzxpDZ
- 2W0XkYPjIosphqx5kpphq/nf8BOGcrMXm0gq7zo0IhBGkMf7wwwVZBYKFsDA
- euS/SKsOiAWH1H08cl1e5S4vERuP8tvV8ajOD9h57uvV4SVuz9WZvV1uieEl
- PSxVWmK/Z6jlRRyEDHNNqcLWoN8Jk3vRicipNOOuiNoikVZPTPcuHiTd8Epa
- UbsdKCP7YVtqSWlDqdikg3V2E5yWMOlsd0JYJeWlGsjtvSP/RoSjRuik5jFW
- CIt/B1CAm+arKS5jLf0shmnKig/I+JjxMetjDiWiKPuoYP4BTGMBi5RruBpL
- Gs4vELgJXekBAAA=
- """,
- """
- androidx/navigation/TestAbstractComp$Companion.class:
- H4sIAAAAAAAA/5VSy27TQBQ9M07zMAHclkfCmzZILRJ1UrErQiqpkCLSIkGV
- TRdo4gxlEnuMPBOry6z4EP6gKyQWKOqSj0LccQJs6ea+zj33+p7xz1/ffwB4
- jicMO0KPslSNzkItcnUqrEp1eCyN3R8am4nIdtPkc8sZoQmqgDEEY5GLMBb6
- NHw7HMvIVuAxlF8orexLBm9re1DHCso+SqgwlOwnZRja/cut2mPobPUnqY2V
- Dsd5EiptZaZFHB7Ij2IaU7sm3jSyaXYosonM9rYHPrhbud6K/oEfkgKlWy83
- jWH1D+FQWjESVlCNJ7lH4jFnas6AgU2ofqZc1qZo1GFozWe+zxvc5wFF81n1
- 4ovXmM92eZu9qlT5xdcyD7jr3WVuQut/tKngLkPtr0D0fUciP6AmpQvCzsSS
- 2t10JBmu95WWR9NkKLNjMYypstZPIxEPRKZcvizWe1rLrBsLYyS9kf8+nWaR
- fK0c1nw31VYlcqCMouZ9rVNb7DHokMwldzt57p6aTnhIWejEIL/y9Buq5wX8
- iGy5KL7BY7L1RQNq8IGAUXRlSX5Gni/J9fNCWEe4tSguCEV0FdcI87BBmV+Q
- 7uE+mtgsFj5Aq/i5SQPqDU7g9bDaw1oP67hBIW72aObtEzCDBpqEG/gGdwzK
- vwGgwqXcGQMAAA==
- """,
- """
- androidx/navigation/TestAbstractComp.class:
- H4sIAAAAAAAA/41RW28SQRT+ZpfrulioN7BeWotI+9CljYmJNCaVxoRIMdGG
- xPA0wIgDy6zZGUgf+S3+g8aHJpoY4qM/ynh2i+2DL7ycb87tO9858/vP958A
- nmOXoczVIAzk4MxTfCaH3MhAeadCm6OeNiHvm0Yw+ZIGY8iP+Ix7PldD711v
- JPomDZshdSiVNK8Y7OpOx0USKQcJpBkS5rPUDJXWKgPqDJnDvr+k2lulpRwZ
- riiVhsuwX22NA0MM3mg28aQyIlTc947FJz71qUFR57RvgvCEh2MR1i/F3nSQ
- wxpD9oqMobaS4uvxdRcFrGdh4RbDdisIh95ImF7IpdIeVyowMYP22oFpT32f
- di3803oiDB9wwylmTWY2fQqLTDYyYGBjip/JyKvRa7BP91zMXccqWo6VX8wd
- K2NlKsXFfNM+sGrsJbNfJ399TVl5K6o+YBFHoc1nxyReqljG3tgwbLyfKiMn
- oqlmUsueL46uZdLPNYKBYFhrSSXa00lPhKecahjWW0Gf+x0eyshfBt2mUiJs
- +FxrQc3Oh2Aa9sUbGeVKyzmd/6YktuheiXjJUnQ+wm3yUoR3CC3CZOyVyfOi
- UxAmdy+QOY/TT5fFwFtUyLqXBcjCIczgxlVzEfEx4f5A7iO7QP4bbp/HERvP
- yDpUlyPGAgmpxtxPsEP4guJ3ifFeF3YTxSZKTdzHBj3xoImHeNQF03iMzS4S
- Go7GlkZKo/AX8BZ2A10DAAA=
- """,
- """
- androidx/navigation/TestClass.class:
- H4sIAAAAAAAA/31Ru04CQRQ9d4BFVlTAF/hs1cJFYqcxUYwJCWKihsZqYDc4
- sMwmzEAs+Rb/wMrEwhBLP8p4d6W2OTmPO/femfn++fgEcIpdwq7U/ihS/oun
- 5UT1pFWR9h4DY+uhNCYLIhT6ciK9UOqed9fpB12bRYrgnCut7AUhdXDYziMD
- x0UaWULaPitD2G/+2/mMUGwOIhsq7d0GVvrSSvbEcJLi1SiGXAwg0ID9FxWr
- KjP/hLA3m7quKAtXFJjNpuXZtCaqdJX5enVEQcRVNYrPFltycs0zlU7mHw8s
- L1iP/ICw0lQ6aI2HnWD0KDshO6Vm1JVhW45UrOem+xCNR93gRsWicj/WVg2D
- tjKK00utI5s0NjiB4PvPN46fg7HMyks0kDl6x8IbE4EKo5OYB9hizP8VIAc3
- ybcT3MRO8kmERc7yT0g1sNTAcgMrKDBFsYESVp9ABmtY59zANdgwcH4BpKME
- iuEBAAA=
- """,
- """
- androidx/navigation/TestClassComp$Companion.class:
- H4sIAAAAAAAA/5VSTW8TMRB99qb5WAJsWz4SvtsGqQW121TcCkiQCilSWiSo
- cukBOYkpTna9aO1EPebED+Ef9ITEAUU98qMQYyfAEfUynnnz3oz3eX/++v4D
- wDM8Zngq9CDP1OAs1mKiToVVmY6PpbGtRBjTytLPDReEJrwExhANxUTEidCn
- 8dveUPZtCQFD8bnSyr5kCDa3ulUsoRiigBJDwX5ShmG7c4k9+wzNzc4os4nS
- 8XCSxkpbmWuRxAfyoxgntpVpY/Nx32b5ochHMt/f6obgbt9qo/+v+SH1XYad
- y01jWP4jOJRWDIQVhPF0EpBtzIWKC2BgI8LPlKt2KRs0GRqzaRjyGg95RNls
- Wr74EtRm0z2+y16Xyvzia5FH3HH3mJuw9l9jSrjLUPnrDl3uSEwOiKG0Z++M
- LPncygaS4XpHaXk0TnsyPxa9hJCVTtYXSVfkytULsNrWWuZ+g6TXCd9n47wv
- 3yjXq78ba6tS2VVGEfmV1pn1ewya5HHBfTid3D0y3f8hVbFzgs6lJ99QPvft
- RxSLHnyBNYrVOQEVhEDEKLuyEG/TyRfi6rl31QluzcG5wGdXcY16AdapCr3o
- Hu6jjg2/8AEa/p8mD4gbnSBoY7mNlTZWcYNS3GzTzNsnYAY11KlvEBrcMSj+
- BuBNy2UQAwAA
- """,
- """
- androidx/navigation/TestClassComp.class:
- H4sIAAAAAAAA/4VRXWsTQRQ9s5vPdWOT+pVYta2NmlZ0myIIpgqaIiykEbQE
- JE+TZIyTbGZlZxL6mN/iPyg+FBQk+OiPEu9uY/vgQ1/umXvn3HPP3Pn95/tP
- AM+ww7DJ1SAK5eDYU3wmh9zIUHlHQptmwLVuhpMvWTCG4ojPuBdwNfTe9Uai
- b7KwGTL7UknzisGubXdcpJFxkEKWIWU+S82w1bpUvcGQ2+8HS53Hl/KrceCK
- 6lm4DPVaaxwaavdGs4knlRGR4oF3ID7xaWCaodImmvZNGB3yaCyixpnNqw4K
- WGHIn4sxPLnc68XshosSVvOwcC1+ZRgNvZEwvYhLpT2uVGiSdu21Q9OeBgG9
- svTP6KEwfMANp5o1mdn0ESwO+TiAgY2pfizjbJdOgzpDdTF3HatsOVZxMXes
- nFVezDfsPWuXvWD2m/SvrxmraMXcPRYrlNp8dkC+pUpMPB0bhrX3U2XkRPhq
- JrXsBeL1hUn6rmY4EAwrLalEezrpieiIE4dhtRX2edDhkYzzZdH1lRJRshVB
- zc6HcBr1xVsZ31WWczr/TUGdtpVKnliJl0e4RVmG8AahRZhOsiplXrwIwvTO
- KXInyfWDJRl4iYcU3TMC8nAIc7hy3lxGskq4P1D4yE5R/IbrJ0nFxiOKDvEK
- pFgiI7VE+z62CZ9T/SYp3urC9lH2UfFxG2t0xB0fd3GvC6axjo0uUhqOxqZG
- RqP0F+8+XexPAwAA
- """,
- """
- androidx/navigation/TestClassWithArg.class:
- H4sIAAAAAAAA/41QTWsTURQ9781kkoyJmcSvNFWrtpQ2Cyct7pRijAgDsUIt
- cZHVSzKkr5m8gXkvocv8FtduBEVwIcGlP0q8b1JcuRBmzr3nvsO5H79+f/8B
- 4Bn2GPaEmmSpnFyFSizlVBiZqvA81qaXCK0/SHPRzaZFMIbgUixFmAg1Dd+N
- LuOxKcJh8F5IJc0Jg3sQHQ4YnIPDQQUFFH24KBEX2ZSBRRX4uFEGR4Wk5kJq
- hv3+//R+Tj2mselaGzKPGOr9WWoSqcK3sRETYQRJ+Hzp0ErMQtkCqOmM6lfS
- sg5lkyOG3nrV8HmT+zxYr3z6eFDyeclprlfHvMNeVRtewFu84/z86PHAPav/
- ZSVSt9xSIfCs1TGzDeqnYvmaxpUqH/3pzNBuvXQSM9T6UsWni/kozs7FKKFK
- o5+ORTIQmbT8uui/TxfZOH4jLdk6Wygj5/FAakmvXaVSkxtrHNHh3Hyphr0j
- ZZzyAjzCHWInxDlFv/0N5fb2V1Q/55pHhFYDtPGY8O5GhZuo2RtRZt1oEwT0
- b7xCezqKhfYXVD/906ayEVzbcDzJ8SF2Kb7Mhyzg1hBOhNsR7kTU9h6laEbY
- QmsIprGN+0MUNWoaDzT8HD2NQKP+B9uGEJmeAgAA
- """,
- """
- androidx/navigation/TestClassWithArgComp$Companion.class:
- H4sIAAAAAAAA/5VSy24TMRQ99qR5DAHSlkfC+xGkFIlOE3VXBCqpkCKlRaJV
- WHSBnMS0TmY8yHaiLrPiQ/iDrpBYoKhLPgpxPQmwpZvre8+5597x8fz89f0H
- gG08Y2gJPTSpGp5FWkzViXAq1dGRtK4dC2s/KHe6a07aafK57oPQRBfAGCoj
- MRVRLPRJ9K4/kgNXQMCQf6m0cq8YgsZGr4wV5EPkUGDIuVNlGba7l1+3w9Bs
- dMepi5WORtMkUtpJo0Uc7clPYhK7dqqtM5OBS82+MGNpdjZ6Ibhfu14f/CM/
- JhnLsHm5aQyrfwT70omhcIIwnkwDMpH5UPIBDGxM+Jny1RZlwyZDfT4LQ17l
- Ia9QNp8VL74E1fmsxbfYm0KRX3zN8wr3vS3mJzT+158C7jKU/ppE33ggpnvU
- qHQm2hw7cr2dDiXD9a7S8mCS9KU5Ev2YkLVuOhBxTxjl6yVY7mgtTbZI0luF
- h+nEDORb5bna+4l2KpE9ZRU172qdumyPRZOszvn708n9k9M1HlIVeUPoXHn+
- DcXzjH5EMZ+Br/GYYnnRgBJCoMIou7IUv6CTL8Xl88xcL7i1ABeCLLuKa8QF
- eEJVmInu4T5qeJotfIB69qOTB9RbOUbQwWoHax2s4waluNmhmbePwSyqqBFv
- EVrcscj/BkiAeOUlAwAA
- """,
- """
- androidx/navigation/TestClassWithArgComp.class:
- H4sIAAAAAAAA/41SW08TQRT+ZnvZ7VpkWxELeEFBLVXZQngSgsEa4yalJkhq
- DE/TdizTbmfN7rThkd/isy9EDYkmhvjojzKe3VZ40AeS3XPmnDnn+85lfv3+
- 9gPABtYYylx1wkB2jlzFR7LLtQyUuy8iXfN5FL2V+nAn7NaCwQcTjMHp8RF3
- fa667utWT7S1iRRDdksqqbcZ0mVvpcmQKq8088jAtJGGRTYPuwzMy8PGlRwM
- 5ClUH8qIoVK/LP8m8XSF3omhiMBjsLba/oR4/bIoy7Hgiq5NXGNYK9f7gSYU
- tzcauFJpESruuy/Eez70dS1QkQ6HbR2Euzzsi3Bz3Nd1GzOYZcidgzFsXLqR
- ixI28yhhLh7IPMNSPQi7bk/oVsililyuVKATlMhtBLox9H0aQeFvvbtC8w7X
- nHzGYJSidbJY5GIBGnaf/Ecytqp06tCmX50dF22jZNiGc3Zs02c4lm1Y6dLZ
- 8aK5blTZU2Y+nypmHWPeqKZ+fswaTnqvcG5ZlDKftjJONsZbZzFLocFHL6hF
- qZJCV/uaYWFvqLQcCE+NZCRbvti5aITWXgs6gmG6LpVoDActEe5zimEo1oM2
- 95s8lLE9ceY9pUSYDFBQsv0mGIZt8VLGd3MTnuY/LFijiaapcwNz8YCp0ApZ
- WdI3SRfjV0g6RXYm8T4ia5uiDdJ25RS5ysJXTJ0kCI8nmcAzPCE5O47CVUzH
- k6ZTjEajgEP/GMuNF0A6U/mCqU//hcmPAyYwFhVlTpJLSFaI/HfMvGOnuPEZ
- CyeJJ4XVhJDR6zOSxtwEewVV0jXy3yLE2wdIebjjYdHDXdyjI5Y8LOP+AViE
- B3h4ACvCdIRyBDuR2QhOhEKE0h8DM7acGQQAAA==
- """,
- """
- androidx/navigation/TestGraph.class:
- H4sIAAAAAAAA/31SXWsTQRQ9M0k2m220af1oYq1Vmwf1wW2Lbxah1g8W1hVs
- CJQ+TbJDOslmVnYnSx/z5A/xHxQfCgoS9M0fJd5Zgz4IzsC995w5c5h7mR8/
- P38F8ARdhi2h4yxV8bmvRaFGwqhU+z2Zm9eZeH9WB2NojUUh/ETokf92MJZD
- U0eFwTlQWplnDJUHD/tN1OB4qKLOUDVnKmfYDv/r/JTBPRgmpYcHbi+6QXTc
- O4yOXjZxBV6DyKsMO2GajfyxNINMKJ37QuvUlF65H6UmmiUJWa2Fk9SQmf9G
- GhELI4jj06JCXTIbGjaAgU2IP1cW7VIV7zF0F3PP423u8RZVi7n7/QNvL+b7
- fJc9r7v820eHt7jV7jPrsBaJ4gU1oXT5iMcTw7D5bqaNmspAFypXg0Qe/n0k
- zeMojSXDaqi0jGbTgcx6gjQM62E6FElfZMriJekdp7NsKF8pCzpL4/4/ttij
- 8VTLnjp2WpTvEHIotyhz2rUSbRPybeeUa48u4V6Ux3eXYqCLexSbvwVokBXg
- YuXP5Q1S27XyBfzkEs1PWL0oCY77ZdzCTvmZaDZksH6KSoBrAa4HuIGbVGIj
- QBudU7Act7BJ5zm8HLdzOL8ATyx+j4kCAAA=
- """,
- """
- androidx/navigation/TestInterface.class:
- H4sIAAAAAAAA/4VOTUvDQBB9s9GmjV+JWqhH8W7a0psnQYRAVVDxktM22ZZt
- 0g10t6HH/i4P0rM/SpzEH+AMvHkz83gz3z+fXwAm6BOupcnXlc63sZG1Xkin
- KxO/K+sS49R6LjPlgwjhUtYyLqVZxC+zpcqcD48QTYvKldrET8rJXDp5RxCr
- 2mNzaqDXAAhU8Hyrm27ILB8R+vtdNxADEYiQ2Xyw343FkJrlmHAz/fcrvsTG
- 0bOsH3isTSu5LRwheKs260w96lIRrl43xumV+tBWz0p1b0zlWqnt8C0c4C8E
- Llo8xyXXETsfcnZSeAn8BN0EPQRMcZTgGCcpyOIUZymERWgR/QKEKxfgUgEA
- AA==
- """,
- """
- androidx/navigation/TestNavHost.class:
- H4sIAAAAAAAA/4VRu04CQRQ9d3koKyrgA/BF7NTCBWKnMfERIwlqooaGamA3
- OLLMJsxAKPkW/8DKxMIQSz/KeHcxxsLEYk7O487MnTsfn69vAA5QIpSEcvuB
- dEeOEkPZEUYGyrn3tLkWw8tAmxkQIfMohsLxheo4N61Hr81ujLD+19afbQlC
- 8kgqaY4JsZ3dRhozmLURR4oQNw9SE7br/1x+SMjWu4HxpXKuPCNcYQR7Vm8Y
- 4/4phFQIIFCX/ZEMVZmZWyFsTsa2bRWsaE3Ghcm4apXpNPH+lLQyVlhU5aI/
- e/h1P9Nz7kiqKNnvGm7/LHA9wmJdKu960Gt5/XvR8tnJ1YO28BuiL0P9bdp3
- waDf9i5kKIq3A2Vkz2tILTk9USow0cEaFVg8HZ7a9D3huBjXWDmRBhJ7L7Cf
- mVhYZ0xGZh4bjOlpAeaYhflmhEVsRf9MmOdsoYlYDYs1ZGrIIscUSzUsY6UJ
- 0lhFnnONtEZBY/YLEThWOyQCAAA=
- """,
- """
- androidx/navigation/TestObject.class:
- H4sIAAAAAAAA/31S0WoTQRQ9M0k2m220aW1tYrVWW0R96LbFN4tQq8LCuoIN
- AenTJDvESTazsDtZ+pgnP8Q/KH0oKEjQNz9KvLNGfRCcgXvvOXPmMPcy3398
- +gLgCXYZtoSOs1TF574WhRoKo1Ltd2Vu3vRHcmDqYAytkSiEnwg99H+zFQbn
- SGllnjFUHj7qNVGD46GKOkPVvFc5w3b4f+unDO7RIClNPHB70w2i0+5xdPKy
- iWvwGkReZ9gJ02zoj6TpZ0Lp3Bdap6Y0y/0oNdE0SchqJRynhsz819KIWBhB
- HJ8UFeqT2dCwAQxsTPy5smifqviAYXc+8zze5h5vUTWfud8+8PZ8dsj32fO6
- y79+dHiLW+0hsw4rkSheUBdKl4/YGxuGzbdTbdREBrpQueon8vjvI2kgJ2ks
- GZZDpWU0nfRl1hWkYVgN04FIeiJTFi9I7zSdZgP5SlnQWRj3/rHFAY2nWvbU
- sdOivEXIodyizGnXSnSXkG87p1x7fAX3ojzeXoiBB7hHsflLgAZZAS6W/lze
- ILVdS5/B312heYnli5LguF/GO9gpvxPNhgxWz1AJcCPAWoB13KQSGwHa6JyB
- 5biFTTrP4eW4ncP5CW9bciiLAgAA
- """
- )
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index f3a2d7e..2698334 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
plugins {
@@ -69,4 +71,5 @@
inceptionYear = "2017"
description = "Android Navigation-Runtime"
legacyDisableKotlinStrictApiMode = true
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/paging/paging-common/build.gradle b/paging/paging-common/build.gradle
index 2bfc291..1c5792b 100644
--- a/paging/paging-common/build.gradle
+++ b/paging/paging-common/build.gradle
@@ -27,9 +27,9 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.konan.target.Family
+
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
androidXMultiplatform {
@@ -39,7 +39,14 @@
ios()
watchos()
tvos()
- android()
+ androidLibrary {
+ namespace = "androidx.paging.common"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ }
defaultPlatform(PlatformIdentifier.JVM)
@@ -150,7 +157,3 @@
metalavaK2UastEnabled = false
samples(project(":paging:paging-samples"))
}
-
-android {
- namespace "androidx.paging.common"
-}
diff --git a/paging/paging-common/lint-baseline.xml b/paging/paging-common/lint-baseline.xml
new file mode 100644
index 0000000..c360e17
--- /dev/null
+++ b/paging/paging-common/lint-baseline.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
+
+ <issue
+ id="NewApi"
+ message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
+ errorLine1=" @OptIn(ExperimentalStdlibApi::class) repeat(result.size) { _hints.removeFirst() }"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/commonTest/kotlin/androidx/paging/PagingDataPresenterTest.kt"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
+ errorLine1=" @OptIn(ExperimentalStdlibApi::class) repeat(result.size) { _hints.removeFirst() }"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/commonTest/kotlin/androidx/paging/PagingDataPresenterTest.kt"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
+ errorLine1=" repeat(data.size) { removeFirst() }"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/commonTest/kotlin/androidx/paging/TestUtils.kt"/>
+ </issue>
+
+ <issue
+ id="BanThreadSleep"
+ message="Uses Thread.sleep()"
+ errorLine1=" Thread.sleep(1000)"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListTest.kt"/>
+ </issue>
+
+ <issue
+ id="BanThreadSleep"
+ message="Uses Thread.sleep()"
+ errorLine1=" @Suppress("BlockingMethodInNonBlockingContext") Thread.sleep(100)"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/commonJvmAndroidTest/kotlin/androidx/paging/SingleRunnerTest.kt"/>
+ </issue>
+
+</issues>
diff --git a/paging/paging-testing/build.gradle b/paging/paging-testing/build.gradle
index 9212202..50837cb 100644
--- a/paging/paging-testing/build.gradle
+++ b/paging/paging-testing/build.gradle
@@ -29,7 +29,6 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
androidXMultiplatform {
@@ -39,7 +38,14 @@
ios()
watchos()
tvos()
- android()
+ androidLibrary {
+ namespace = "androidx.paging.testing"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ }
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -131,6 +137,3 @@
metalavaK2UastEnabled = false
}
-android {
- namespace "androidx.paging.testing"
-}
diff --git a/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt
index 98eae1f..22f92bc 100644
--- a/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt
+++ b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt
@@ -113,7 +113,6 @@
scenario.close()
}
- @Test
fun testPdfViewerFragment_isTextSearchActive_toggleMenu() {
val scenario =
scenarioLoadDocument(
@@ -160,7 +159,6 @@
scenario.close()
}
- @Test
fun testPdfViewerFragment_setDocumentUri_passwordProtected_portrait() {
val scenario =
scenarioLoadDocument(
diff --git a/pdf/integration-tests/testapp/src/main/AndroidManifest.xml b/pdf/integration-tests/testapp/src/main/AndroidManifest.xml
index 309210b..e77d742 100644
--- a/pdf/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/pdf/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -24,6 +24,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
+ android:name=".PdfSampleApplication"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
diff --git a/pdf/integration-tests/testapp/src/main/kotlin/androidx/pdf/testapp/PdfSampleApplication.kt b/pdf/integration-tests/testapp/src/main/kotlin/androidx/pdf/testapp/PdfSampleApplication.kt
new file mode 100644
index 0000000..5696ffb
--- /dev/null
+++ b/pdf/integration-tests/testapp/src/main/kotlin/androidx/pdf/testapp/PdfSampleApplication.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2024 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.pdf.testapp
+
+import android.app.Application
+import com.google.android.material.color.DynamicColors
+
+class PdfSampleApplication : Application() {
+ override fun onCreate() {
+ super.onCreate()
+ // Apply dynamic colors to activities if available
+ DynamicColors.applyToActivitiesIfAvailable(this)
+ }
+}
diff --git a/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml b/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml
index 3cf2d58..badec27 100644
--- a/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -40,9 +40,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/launch_string"
- android:textColor="@color/google_white"
- app:backgroundTint="@color/google_grey"
- app:strokeColor="@color/google_white"
app:strokeWidth="1dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
@@ -55,9 +52,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/search_string"
- android:textColor="@color/google_white"
- app:backgroundTint="@color/google_grey"
- app:strokeColor="@color/google_white"
app:strokeWidth="1dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
diff --git a/pdf/pdf-viewer-fragment/build.gradle b/pdf/pdf-viewer-fragment/build.gradle
index f412d40..8ca6798 100644
--- a/pdf/pdf-viewer-fragment/build.gradle
+++ b/pdf/pdf-viewer-fragment/build.gradle
@@ -33,6 +33,9 @@
api(libs.kotlinStdlib)
api(project(":pdf:pdf-viewer"))
api("androidx.core:core:1.13.1")
+
+ implementation("androidx.fragment:fragment-ktx:1.8.1")
+ implementation("com.google.android.material:material:1.11.0")
}
android {
diff --git a/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt b/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt
index 5b4c550..d31fa8e 100644
--- a/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt
+++ b/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt
@@ -247,7 +247,6 @@
fastScrollView = pdfViewer?.findViewById(R.id.fast_scroll_view)
loadingView = pdfViewer?.findViewById(R.id.loadingView)
paginatedView = fastScrollView?.findViewById(R.id.pdf_view)
- paginationModel = paginatedView!!.paginationModel
zoomView = pdfViewer?.findViewById(R.id.zoom_view)
findInFileView = pdfViewer?.findViewById(R.id.search)
findInFileView!!.setPaginatedView(paginatedView!!)
@@ -295,7 +294,7 @@
}
annotationButton?.let { button ->
if ((savedInstanceState == null) && isAnnotationIntentResolvable) {
- button.visibility = View.VISIBLE
+ button.show()
}
}
},
@@ -485,7 +484,7 @@
isAnnotationIntentResolvable &&
state.getBoolean(KEY_ANNOTATION_BUTTON_VISIBILITY)
) {
- annotationButton?.visibility = View.VISIBLE
+ annotationButton?.show()
}
}
}
@@ -553,6 +552,7 @@
SingleTapHandler(
requireContext(),
annotationButton!!,
+ paginatedView!!,
findInFileView!!,
zoomView!!,
selectionModel,
@@ -574,7 +574,7 @@
}
private fun refreshContentAndModels(pdfLoader: PdfLoader) {
- paginationModel = paginatedView!!.initPaginationModelAndPageRangeHandler(requireActivity())
+ paginationModel = paginatedView!!.model
paginatedView?.setPdfLoader(pdfLoader)
findInFileView?.setPdfLoader(pdfLoader)
@@ -640,16 +640,16 @@
if (!documentLoaded) {
return
}
+ setAnnotationIntentResolvability()
+ if (!isAnnotationIntentResolvable && annotationButton?.visibility == View.VISIBLE) {
+ annotationButton?.post { annotationButton?.hide() }
+ }
if (
isAnnotationIntentResolvable &&
annotationButton?.visibility != View.VISIBLE &&
findInFileView?.visibility != View.VISIBLE
) {
- annotationButton?.post {
- annotationButton?.visibility = View.VISIBLE
- annotationButton?.alpha = 0f
- annotationButton?.animate()?.alpha(1f)?.setDuration(200)?.start()
- }
+ annotationButton?.post { annotationButton?.show() }
}
}
@@ -705,10 +705,7 @@
private fun detachViewsAndObservers() {
zoomScrollObserver?.let { zoomView?.zoomScroll()?.removeObserver(it) }
- paginatedView?.let { view ->
- view.removeAllViews()
- paginationModel?.removeObserver(view)
- }
+ paginatedView?.let { view -> view.removeAllViews() }
}
override fun onDestroyView() {
@@ -753,6 +750,7 @@
}
if (pdfLoader != null) {
pdfLoaderCallbacks?.uri = fileUri
+ paginatedView?.resetModels()
destroyContentModel()
}
detachViewsAndObservers()
@@ -765,7 +763,7 @@
onLoadDocumentError(e)
}
if (localUri != null && localUri != fileUri) {
- annotationButton?.visibility = View.GONE
+ annotationButton?.hide()
}
localUri = fileUri
}
diff --git a/pdf/pdf-viewer/build.gradle b/pdf/pdf-viewer/build.gradle
index fb9117f..78bb274 100644
--- a/pdf/pdf-viewer/build.gradle
+++ b/pdf/pdf-viewer/build.gradle
@@ -24,23 +24,22 @@
}
dependencies {
- api(libs.guavaAndroid)
api(libs.kotlinCoroutinesCore)
- api("androidx.fragment:fragment-ktx:1.8.1")
- api("com.google.android.material:material:1.11.0")
implementation(libs.kotlinStdlib)
implementation("androidx.exifinterface:exifinterface:1.3.2")
+ implementation("androidx.core:core:1.13.0")
+ implementation("androidx.annotation:annotation:1.7.0")
+ implementation("com.google.android.material:material:1.11.0")
+ implementation("com.google.errorprone:error_prone_annotations:2.30.0")
testImplementation(project(":pdf:pdf-viewer-fragment"))
testImplementation(libs.junit)
testImplementation(libs.testCore)
testImplementation(libs.testRunner)
- testImplementation(libs.junit)
testImplementation(libs.mockitoCore4)
testImplementation(libs.robolectric)
testImplementation(libs.truth)
- testImplementation(libs.guavaTestlib)
testImplementation(libs.testExtTruth)
testImplementation(libs.testExtJunitKtx)
testImplementation("androidx.fragment:fragment-testing:1.7.1")
diff --git a/pdf/pdf-viewer/src/androidTest/AndroidManifest.xml b/pdf/pdf-viewer/src/androidTest/AndroidManifest.xml
index fe5abb1..fc250fb 100644
--- a/pdf/pdf-viewer/src/androidTest/AndroidManifest.xml
+++ b/pdf/pdf-viewer/src/androidTest/AndroidManifest.xml
@@ -19,6 +19,7 @@
<application>
<activity
android:name="androidx.pdf.TestActivity"
- android:exported="false"/>
+ android:exported="false"
+ android:theme="@style/Theme.Material3.DynamicColors.DayNight"/>
</application>
</manifest>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/widget/FastScrollViewIntegrationTest.kt b/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/widget/FastScrollViewIntegrationTest.kt
index f15fb9f..647e77c 100644
--- a/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/widget/FastScrollViewIntegrationTest.kt
+++ b/pdf/pdf-viewer/src/androidTest/java/androidx/pdf/widget/FastScrollViewIntegrationTest.kt
@@ -55,13 +55,12 @@
// Start by adding a PaginatedView with 10 50x50 pages
paginatedView =
PaginatedView(activity).apply {
- initPaginationModelAndPageRangeHandler(activity).apply {
+ model.apply {
initialize(10)
for (i in 0..9) {
addPage(i, Dimensions(50, 50))
}
}
- model = paginationModel
}
// Add a ZoomView to host the PaginatedView
zoomView =
@@ -73,7 +72,7 @@
fastScrollView =
FastScrollView(activity).apply {
layoutParams = ViewGroup.LayoutParams(100, 400)
- setPaginationModel(paginatedView.paginationModel)
+ setPaginationModel(paginatedView.model)
addView(zoomView)
}
activity.setContentView(fastScrollView)
@@ -92,12 +91,20 @@
// Overscroll the bottom
zoomView.scrollTo(0, 2000, true)
assertThat(pageIndicator.visibility).isEqualTo(View.VISIBLE)
- assertThat(pageIndicator.text).isEqualTo("9-10 / 10")
+
+ // Verify if the text indicates the last page with the pattern "<number>-10 / 10"
+ val bottomPageText = pageIndicator.text.toString()
+ val bottomPattern = Regex("\\d+-10 / 10")
+ assertThat(bottomPattern.containsMatchIn(bottomPageText)).isTrue()
// Overscroll the top
zoomView.scrollTo(0, -50, true)
assertThat(pageIndicator.visibility).isEqualTo(View.VISIBLE)
- assertThat(pageIndicator.text).isEqualTo("1-3 / 10")
+
+ // Verify if the text indicates the first page with the pattern "1-<number> / 10"
+ val topPageText = pageIndicator.text.toString()
+ val topPattern = Regex("1-\\d+ / 10")
+ assertThat(topPattern.containsMatchIn(topPageText)).isTrue()
}
}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/data/UiFutureValues.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/data/UiFutureValues.java
index 0021531..5f471cb 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/data/UiFutureValues.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/data/UiFutureValues.java
@@ -24,10 +24,10 @@
import androidx.pdf.data.FutureValues.SettableFutureValue;
import androidx.pdf.util.ThreadUtils;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Helpers to create {@link FutureValue}s that are ready to be used for UI operations: their
@@ -58,8 +58,16 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class UiFutureValues {
private static final String TAG = UiFutureValues.class.getSimpleName();
+
private static final Executor DEFAULT_EXECUTOR = Executors.newFixedThreadPool(4,
- new ThreadFactoryBuilder().setNameFormat("PdfViewer-" + TAG + "-%d").build());
+ new ThreadFactory() {
+ private final AtomicInteger mCount = new AtomicInteger(1);
+
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "PdfViewer-" + TAG + "-" + mCount.getAndIncrement());
+ }
+ });
private static Executor sExecutor = DEFAULT_EXECUTOR;
private UiFutureValues() {
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/find/FindInFileView.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/find/FindInFileView.java
index 4a24ed8..46be5f5 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/find/FindInFileView.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/find/FindInFileView.java
@@ -31,6 +31,7 @@
import android.widget.TextView.OnEditorActionListener;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.pdf.R;
import androidx.pdf.util.Accessibility;
@@ -43,7 +44,6 @@
import com.google.android.material.floatingactionbutton.FloatingActionButton;
-import javax.annotation.Nullable;
/**
* A View that has a search query box, find-next and find-previous button, useful for finding
@@ -206,7 +206,7 @@
if (visibility) {
this.setVisibility(VISIBLE);
if (mAnnotationButton != null && mAnnotationButton.getVisibility() == VISIBLE) {
- mAnnotationButton.setVisibility(GONE);
+ mAnnotationButton.hide();
}
setupFindInFileBtn();
} else {
@@ -233,7 +233,7 @@
mQueryBox.setText("");
parentLayout.setVisibility(GONE);
if (mIsAnnotationIntentResolvable) {
- mAnnotationButton.setVisibility(VISIBLE);
+ mAnnotationButton.show();
}
});
}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLink.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLink.java
index 44ac3b1..f020ed4 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLink.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLink.java
@@ -27,8 +27,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
-
-import com.google.common.base.Preconditions;
+import androidx.core.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLinkDestination.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLinkDestination.java
index e7eae7f..f8e6af0 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLinkDestination.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/models/GotoLinkDestination.java
@@ -26,8 +26,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-
-import com.google.common.base.Preconditions;
+import androidx.core.util.Preconditions;
/**
* Represents the content associated with the destination where a goto link is directing.
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/select/SelectionActionMode.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/select/SelectionActionMode.java
index be8682c..984cebb 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/select/SelectionActionMode.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/select/SelectionActionMode.java
@@ -37,6 +37,7 @@
import androidx.pdf.viewer.PageMosaicView;
import androidx.pdf.viewer.PageViewFactory;
import androidx.pdf.viewer.PaginatedView;
+import androidx.pdf.viewer.PdfSelectionHandles;
import java.util.Objects;
@@ -174,10 +175,39 @@
*/
@Override
public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
- PageSelection pageSelection = mSelectionModel.selection().get();
- Rect bounds = pageSelection.getRects().get(0);
+ Rect bounds = getBoundsToPlaceMenu();
outRect.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
}
+
+ private Rect getBoundsToPlaceMenu() {
+ PageSelection pageSelection = mSelectionModel.mSelection.get();
+ int selectionPage = pageSelection.getPage();
+ PdfSelectionHandles mSelectionHandles = mPaginatedView.getSelectionHandles();
+ Rect startHandlerect = new Rect();
+ mSelectionHandles.getStartHandle().getGlobalVisibleRect(startHandlerect);
+
+ Rect stopHandleRect = new Rect();
+ mSelectionHandles.getStopHandle().getGlobalVisibleRect(stopHandleRect);
+
+ int screenWidth = mPaginatedView.getResources().getDisplayMetrics().widthPixels;
+ int screenHeight = mPaginatedView.getResources().getDisplayMetrics().heightPixels;
+
+ if (pageSelection.getRects().size() == 1 || startHandlerect.intersect(0, 0, screenWidth,
+ screenHeight)) {
+ return pageSelection.getRects().getFirst();
+ } else if (stopHandleRect.intersect(0, 0, screenWidth, screenHeight)) {
+ return pageSelection.getRects().getLast();
+ } else {
+ // Center of the view in page coordinates
+ int viewCentreX = mPaginatedView.getViewArea().centerX()
+ * mPaginatedView.getModel().getPageSize(selectionPage).getWidth()
+ / mPaginatedView.getModel().getWidth();
+ int viewCentreY = mPaginatedView.getViewArea().centerY()
+ - mPaginatedView.getModel().getPageLocation(selectionPage,
+ mPaginatedView.getViewArea()).top;
+ return new Rect(viewCentreX, viewCentreY, viewCentreX, viewCentreY);
+ }
+ }
}
}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/util/ThreadUtils.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/util/ThreadUtils.java
index b006b77..02a6bec 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/util/ThreadUtils.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/util/ThreadUtils.java
@@ -22,11 +22,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
/** Thread-related utilities. */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class ThreadUtils {
@@ -35,11 +30,6 @@
public static final Handler UI_THREAD_HANDLER = new Handler(Looper.getMainLooper());
- /** The executor for background tasks. Makes it easier to test if it's sequential. */
- private static final Executor BACKGROUND_EXECUTOR =
- Executors.newSingleThreadExecutor(
- new ThreadFactoryBuilder().setNameFormat("PdfViewerThreadUtils-%d").build());
-
/**
* Checks if the running thread is the UI thread.
*
@@ -61,11 +51,6 @@
}
}
- /** Runs the given {@link Runnable} on a background thread. */
- public static void runInBackground(@NonNull Runnable r) {
- BACKGROUND_EXECUTOR.execute(r);
- }
-
/**
* Posts the given runnable on the UI thread, to be started after the given delay (milliseconds)
*/
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/AbstractPaginatedView.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/AbstractPaginatedView.java
deleted file mode 100644
index 6575393..0000000
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/AbstractPaginatedView.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2024 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.pdf.viewer;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-
-/**
- * Base class for views that will base their display on the {@link PaginationModel}.
- *
- * <p>Provides consistent {@link #onMeasure(int, int)} and {@link #onLayout(boolean, int, int, int,
- * int)} behavior and requests a layout each time a new page is added to the model.
- *
- * <p>Subclasses must implement {@link #layoutChild(int)} in order to position their actual views.
- * Subclasses can override {@link #onViewAreaChanged()} if they need to perform updates when this
- * happens.
- *
- * <p>Padding will not be acknowledged. If views must implement padding they need to measure
- * themselves but should be aware they will diverge from the coordinates of other views using the
- * {@link PaginationModel}.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public abstract class AbstractPaginatedView extends ViewGroup implements PaginationModelObserver {
-
- @Nullable
- private PaginationModel mModel;
-
- public AbstractPaginatedView(@NonNull Context context) {
- super(context);
- }
-
- public AbstractPaginatedView(@NonNull Context context, @NonNull AttributeSet attrs) {
- super(context, attrs);
- }
-
- public AbstractPaginatedView(@NonNull Context context, @Nullable AttributeSet attrs,
- int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public void setModel(@Nullable PaginationModel model) {
- this.mModel = model;
- }
-
- // This class does not produce a model but rather renders a model generated elsewhere to a view.
- // Any classes wishing to obtain the model should do so from the owner/manager.
- @Nullable
- public PaginationModel getModel() {
- return mModel;
- }
-
- protected boolean isInitialized() {
- return mModel != null;
- }
-
- /**
- * Measures this view in relation to the {@link #mModel} then asks all child views to measure
- * themselves.
- *
- * <p>If the {@link #mModel} is not initialized, this view has nothing to display and will
- * measure (0, 0). Otherwise, view will measure ({@link #mModel}'s width, {@link #mModel}'s
- * estimated height).
- */
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = 0;
- int estimatedHeight = 0;
-
- if (isInitialized()) {
- width = mModel.getWidth();
- estimatedHeight = mModel.getEstimatedFullHeight();
- }
-
- setMeasuredDimension(width, estimatedHeight);
- measureChildren(widthMeasureSpec, heightMeasureSpec);
- }
-
- /**
- * Provides consistent layout behavior for subclasses.
- *
- * <p>Does not perform a layout if there aren't any child views. Otherwise asks the
- * subclasses to
- * layout each child by index.
- */
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int count = getChildCount();
- if (count == 0) {
- return;
- }
-
- for (int i = 0; i < count; i++) {
- layoutChild(i);
- }
- }
-
- /**
- * Lays out the child at {@code index}.
- *
- * <p>Subclasses should use the {@link #mModel} to determine top and bottom values.
- */
- protected abstract void layoutChild(int index);
-
- /** Requests a layout because this view has to grow now to accommodate the new page(s). */
- @Override
- public void onPageAdded() {
- requestLayout();
- }
-
- /**
- * Implementation of PaginationModelObserver, is no-op at this level.
- *
- * <p>Will be called each time the viewArea of the model is changed. Should be overridden by any
- * subclasses that wish to perform actions when this occurs.
- */
- @Override
- public void onViewAreaChanged() {
- }
-}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/AccessibilityPageWrapper.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/AccessibilityPageWrapper.java
index 83247d4..0dc9a7d 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/AccessibilityPageWrapper.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/AccessibilityPageWrapper.java
@@ -84,7 +84,6 @@
mPageLinksView.setPageUrlLinks(links);
}
- @NonNull
@Override
public void setPageGotoLinks(@Nullable List<GotoLink> links) {
mPageView.setPageGotoLinks(links);
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageRangeHandler.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageRangeHandler.java
index 8936f4e..ea3ece28 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageRangeHandler.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PageRangeHandler.java
@@ -56,11 +56,6 @@
mMaxPage = maxPage;
}
- @NonNull
- public PaginationModel getPaginationModel() {
- return mPaginationModel;
- }
-
/**
* Returns the page currently roughly centered in the view.
*/
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java
index 0ab2bfe..3cdd35d 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java
@@ -21,10 +21,12 @@
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
+import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.pdf.ViewState;
import androidx.pdf.data.Range;
import androidx.pdf.util.PaginationUtils;
@@ -45,14 +47,11 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@SuppressWarnings("WrongCall")
-public class PaginatedView extends AbstractPaginatedView {
-
- private static final String TAG = PaginatedView.class.getSimpleName();
-
+public class PaginatedView extends ViewGroup implements PaginationModelObserver {
/** Maps the current child views to pages. */
private final SparseArray<PageView> mPageViews = new SparseArray<>();
- private PaginationModel mPaginationModel;
+ private PaginationModel mModel;
private PageRangeHandler mPageRangeHandler;
@@ -68,6 +67,9 @@
private boolean mIsConfigurationChanged = false;
+ /** The current viewport in content coordinates */
+ private final Rect mViewArea = new Rect();
+
public PaginatedView(@NonNull Context context) {
this(context, null);
}
@@ -78,20 +80,112 @@
public PaginatedView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
-
+ mModel = new PaginationModel(context);
+ mPageRangeHandler = new PageRangeHandler(mModel);
}
- /** Instantiate PaginationModel and PageRangeHandler */
- @NonNull
- public PaginationModel initPaginationModelAndPageRangeHandler(@NonNull Context context) {
- mPaginationModel = new PaginationModel(context);
- mPageRangeHandler = new PageRangeHandler(mPaginationModel);
- return mPaginationModel;
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mModel.addObserver(this);
+ }
+
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mPageRangeHandler != null) {
+ mPageRangeHandler.setVisiblePages(null);
+ }
+ mModel.removeObserver(this);
+ }
+
+ @VisibleForTesting
+ public void setModel(@NonNull PaginationModel model) {
+ mModel = model;
}
@NonNull
- public PaginationModel getPaginationModel() {
- return mPaginationModel;
+ public PaginationModel getModel() {
+ return mModel;
+ }
+
+ @NonNull
+ public PaginationModel resetModels() {
+ mModel = new PaginationModel(getContext());
+ mPageRangeHandler = new PageRangeHandler(mModel);
+ return mModel;
+ }
+
+ /** Requests a layout because this view has to grow now to accommodate the new page(s). */
+ @Override
+ public void onPageAdded() {
+ requestLayout();
+ }
+
+ protected boolean isInitialized() {
+ return mModel != null;
+ }
+
+ /**
+ * Measures this view in relation to the {@link #mModel} then asks all child views to measure
+ * themselves.
+ *
+ * <p>If the {@link #mModel} is not initialized, this view has nothing to display and will
+ * measure (0, 0). Otherwise, view will measure ({@link #mModel}'s width, {@link #mModel}'s
+ * estimated height).
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = 0;
+ int estimatedHeight = 0;
+
+ if (isInitialized()) {
+ width = mModel.getWidth();
+ estimatedHeight = mModel.getEstimatedFullHeight();
+ }
+
+ setMeasuredDimension(width, estimatedHeight);
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ /**
+ * Provides consistent layout behavior for subclasses.
+ *
+ * <p>Does not perform a layout if there aren't any child views. Otherwise asks the
+ * subclasses to
+ * layout each child by index.
+ */
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int count = getChildCount();
+ if (count == 0) {
+ return;
+ }
+
+ for (int i = 0; i < count; i++) {
+ layoutChild(i);
+ }
+ }
+
+ /**
+ * Returns the current viewport in content coordinates
+ */
+ @NonNull
+ public Rect getViewArea() {
+ return mViewArea;
+ }
+
+ /**
+ * Updates the current viewport
+ *
+ * @param viewArea the viewport in content coordinates
+ */
+ public void setViewArea(@NonNull Rect viewArea) {
+ if (!viewArea.equals(this.mViewArea)) {
+ this.mViewArea.set(viewArea);
+ onViewAreaChanged();
+ }
}
@NonNull
@@ -120,7 +214,7 @@
@NonNull
public PdfSelectionHandles getSelectionHandles() {
- return mSelectionHandles;
+ return mSelectionHandles;
}
public void setSelectionHandles(@NonNull PdfSelectionHandles selectionHandles) {
@@ -243,10 +337,10 @@
*
* @param index the index of the child view in this ViewGroup
*/
- @Override
- protected void layoutChild(int index) {
+ private void layoutChild(int index) {
int pageNum = mPageViews.keyAt(index);
- Rect pageCoordinates = getModel().getPageLocation(pageNum);
+ Rect viewArea = getViewArea();
+ Rect pageCoordinates = getModel().getPageLocation(pageNum, viewArea);
PageView child = (PageView) getChildAt(index);
child
@@ -257,7 +351,6 @@
pageCoordinates.right,
pageCoordinates.bottom);
- Rect viewArea = getModel().getViewArea();
child
.getPageView()
.setViewArea(
@@ -275,38 +368,13 @@
}
/** Perform a layout when the viewArea of the {@code model} has changed. */
- @Override
- public void onViewAreaChanged() {
+ private void onViewAreaChanged() {
// We can't wait for the next layout pass, the pages will be drawn before.
// We could still optimize to skip the next layoutChild() calls for the pages that have been
// laid out already for this viewArea.
onLayout(false, getLeft(), getTop(), getRight(), getBottom());
}
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
- if (getVisibility() == View.VISIBLE && mPageRangeHandler != null) {
- mPageRangeHandler.adjustMaxPageToUpperVisibleRange();
- if (getChildCount() > 0) {
- for (PageMosaicView page : getChildViews()) {
- page.clearTiles();
- if (mPdfLoader != null) {
- mPdfLoader.cancelAllTileBitmaps(page.getPageNum());
- }
- }
- }
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mPageRangeHandler != null) {
- mPageRangeHandler.setVisiblePages(null);
- }
- }
-
/**
* Refreshes the page range for the visible area.
*/
@@ -350,7 +418,7 @@
if (getViewAt(pageNum) == null) {
mPageViewFactory.getOrCreatePageView(pageNum,
PaginationUtils.getPageElevationInPixels(getContext()),
- mPaginationModel.getPageSize(pageNum));
+ mModel.getPageSize(pageNum));
requiresLayoutPass = true;
}
}
@@ -400,7 +468,7 @@
PageMosaicView pageView = mPageViewFactory.getOrCreatePageView(
page,
PaginationUtils.getPageElevationInPixels(getContext()),
- mPaginationModel.getPageSize(page));
+ mModel.getPageSize(page));
pageView.clearTiles();
pageView.requestFastDrawAtZoom(stableZoom);
pageView.refreshPageContentAndOverlays();
@@ -412,7 +480,7 @@
PageMosaicView pageView = mPageViewFactory.getOrCreatePageView(
page,
PaginationUtils.getPageElevationInPixels(getContext()),
- mPaginationModel.getPageSize(page));
+ mModel.getPageSize(page));
pageView.requestDrawAtZoom(stableZoom);
pageView.refreshPageContentAndOverlays();
}
@@ -433,7 +501,7 @@
PageMosaicView pageView = mPageViewFactory.getOrCreatePageView(
page,
PaginationUtils.getPageElevationInPixels(getContext()),
- mPaginationModel.getPageSize(page));
+ mModel.getPageSize(page));
pageView.requestTiles();
}
}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModel.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModel.java
index f11e659..8407a743 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModel.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModel.java
@@ -50,19 +50,14 @@
* <ol>
* <li>{@link #initialize(int)} with the number of pages it will contain.
* <li>{@link #addPage(int, Dimensions)} to set the dimensions for each page.
- * <li>{@link #setViewArea(Rect)} to report current visible area so pages can be moved
- * horizontally for maximum visibility.
* </ol>
*
* <p>This model is observable. Any classes implementing {@link PaginationModelObserver} can
* register themselves via {@link #addObserver(PaginationModelObserver)} and will be notified when
- * pages are added or the {@link #mViewArea} is changed.
+ * pages are added
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class PaginationModel {
-
- private static final String TAG = PaginationModel.class.getSimpleName();
-
/**
* The spacing added before and after each page (the actual space between 2 consecutive pages is
* twice this distance), in pixels.
@@ -86,22 +81,6 @@
private int mAccumulatedPageSize = 0;
- /**
- * The portion of this model that is currently (or last we knew) exposed on the screen.
- *
- * <p>In the co-ordinates of this model - so if this entire model is within the visible area,
- * then
- * {@code viewArea} will contain the rect Rect(0, 0, getWidth, getHeight). Current visible area
- * should be reported to this model via {@link #setViewArea(Rect)}.
- */
- private final Rect mViewArea = new Rect();
-
- /**
- * A temp working instance for computing {@link #mViewArea} to avoid excessive object
- * creation.
- */
- private final Rect mTempViewArea = new Rect();
-
private final Set<PaginationModelObserver> mObservers = new HashSet<>();
public PaginationModel(@NonNull Context context) {
@@ -284,21 +263,7 @@
mMaxPages - mSize + 1));
}
- /**
- * Updates the portion of this model that is visible on the screen, in this model's
- * coordinates -
- * so relative to (0, 0)-(getWidth(), getHeight()).
- */
- public void setViewArea(@NonNull Rect viewArea) {
- mTempViewArea.set(viewArea);
- if (!mTempViewArea.intersect(
- 0, 0, getWidth(), getEstimatedFullHeight())) { // Modifies tempViewArea.
- }
- if (!mTempViewArea.equals(this.mViewArea)) {
- this.mViewArea.set(mTempViewArea);
- notifyViewAreaChanged();
- }
- }
+
/**
* Returns the location of the page in the model.
@@ -313,10 +278,11 @@
* </ul>
*
* @param pageNum - index of requested page
+ * @param viewArea - the current viewport in content coordinates
* @return - coordinates of the page within this model
*/
@NonNull
- public Rect getPageLocation(int pageNum) {
+ public Rect getPageLocation(int pageNum, @NonNull Rect viewArea) {
int left = 0;
int right = getWidth();
int top = mPageStops[pageNum];
@@ -324,15 +290,15 @@
int width = mPages[pageNum].getWidth();
if (width < right - left) {
// this page is smaller than the view's width, it may slide left or right.
- if (width < mViewArea.width()) {
+ if (width < viewArea.width()) {
// page is smaller than the view: center (but respect min left margin)
- left = Math.max(left, mViewArea.left + (mViewArea.width() - width) / 2);
+ left = Math.max(left, viewArea.left + (viewArea.width() - width) / 2);
} else {
// page is larger than view: scroll proportionally between the margins.
- if (mViewArea.right > right) {
+ if (viewArea.right > right) {
left = right - width;
- } else if (mViewArea.left > left) {
- left = mViewArea.left * (right - width) / (right - mViewArea.width());
+ } else if (viewArea.left > left) {
+ left = viewArea.left * (right - width) / (right - viewArea.width());
}
}
right = left + width;
@@ -371,23 +337,6 @@
return mMaxPages;
}
- /**
- * Returns the intersection of this model and the last viewArea that was reported to this model
- * via {@link #setViewArea(Rect)}.
- */
- @NonNull
- public Rect getViewArea() {
- return mViewArea;
- }
-
- /** Notify all observers that the {@code viewArea} has changed. */
- private void notifyViewAreaChanged() {
- Iterator<PaginationModelObserver> iterator = iterator();
- while (iterator.hasNext()) {
- iterator.next().onViewAreaChanged();
- }
- }
-
/** Notify all observers that a page has been added to the model. */
private void notifyPageAdded() {
Iterator<PaginationModelObserver> iterator = iterator();
@@ -432,7 +381,7 @@
@NonNull
public Iterator<PaginationModelObserver> iterator() {
synchronized (mObservers) {
- return new ArrayList<PaginationModelObserver>(mObservers).iterator();
+ return new ArrayList<>(mObservers).iterator();
}
}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModelObserver.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModelObserver.java
index 9619bfe..c404f42 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModelObserver.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginationModelObserver.java
@@ -35,13 +35,4 @@
* Implementations are free to use this information as desired.
*/
default void onPageAdded() {}
-
- /**
- * Notifies the implementation that the {@code viewArea} of the {@link PaginationModel} has
- * changed.
- *
- * <p>The {@link PaginationModel} does not enforce any implementation expectations.
- * Implementations are free to use this information as desired.
- */
- default void onViewAreaChanged() {}
}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
index 405014c..925bdf2 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
@@ -94,4 +94,14 @@
protected void onDragHandleUp() {
mSelectionActionMode.resume();
}
+
+ @NonNull
+ public ImageView getStartHandle() {
+ return mStartHandle;
+ }
+
+ @NonNull
+ public ImageView getStopHandle() {
+ return mStopHandle;
+ }
}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfViewer.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfViewer.java
index 0fe8777..02df1c8 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfViewer.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfViewer.java
@@ -229,7 +229,7 @@
mFindInFileView = mPdfViewer.findViewById(R.id.search);
mFastScrollView = mPdfViewer.findViewById(R.id.fast_scroll_view);
mPaginatedView = mPdfViewer.findViewById(R.id.pdf_view);
- mPaginationModel = mPaginatedView.getPaginationModel();
+ mPaginationModel = mPaginatedView.getModel();
mZoomView = mPdfViewer.findViewById(R.id.zoom_view);
mLoadingSpinner = mPdfViewer.findViewById(R.id.progress_indicator);
setUpEditFab();
@@ -279,7 +279,7 @@
new SearchQueryObserver(mPaginatedView);
mSearchModel.query().addObserver(mSearchQueryObserver);
- mSingleTapHandler = new SingleTapHandler(getContext(), mAnnotationButton,
+ mSingleTapHandler = new SingleTapHandler(getContext(), mAnnotationButton, mPaginatedView,
mFindInFileView, mZoomView, mSelectionModel, mPaginationModel, mLayoutHandler);
mPageViewFactory = new PageViewFactory(requireContext(), mPdfLoader,
mPaginatedView, mZoomView, mSingleTapHandler, mFindInFileView);
@@ -378,7 +378,6 @@
if (mPaginatedView != null) {
mPaginatedView.removeAllViews();
- mPaginationModel.removeObserver(mPaginatedView);
mPaginatedView = null;
}
@@ -627,8 +626,6 @@
mPaginatedView.getPageRangeHandler().setMaxPage(1);
if (viewState().get() != ViewState.NO_VIEW) {
mPaginationModel.initialize(numPages);
- mPaginatedView.setModel(mPaginationModel);
- mPaginationModel.addObserver(mPaginatedView);
mFastScrollView.setPaginationModel(mPaginationModel);
dismissPasswordDialog();
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SingleTapHandler.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SingleTapHandler.java
index 74f02a6..67cfb2d 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SingleTapHandler.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/SingleTapHandler.java
@@ -38,6 +38,7 @@
public class SingleTapHandler {
private final Context mContext;
private final FloatingActionButton mFloatingActionButton;
+ private final PaginatedView mPaginatedView;
private final FindInFileView mFindInFileView;
private final ZoomView mZoomView;
private final PdfSelectionModel mPdfSelectionModel;
@@ -47,6 +48,7 @@
public SingleTapHandler(@NonNull Context context,
@NonNull FloatingActionButton floatingActionButton,
+ @NonNull PaginatedView paginatedView,
@NonNull FindInFileView findInFileView,
@NonNull ZoomView zoomView,
@NonNull PdfSelectionModel pdfSelectionModel,
@@ -54,6 +56,7 @@
@NonNull LayoutHandler layoutHandler) {
mContext = context;
mFloatingActionButton = floatingActionButton;
+ mPaginatedView = paginatedView;
mFindInFileView = findInFileView;
mZoomView = zoomView;
mPdfSelectionModel = pdfSelectionModel;
@@ -65,15 +68,14 @@
mIsAnnotationIntentResolvable = annotationIntentResolvable;
}
- /** */
public void handleSingleTapConfirmedEventOnPage(@NonNull MotionEvent event,
@NonNull PageMosaicView pageMosaicView) {
if (mIsAnnotationIntentResolvable) {
if (mFloatingActionButton.getVisibility() == View.GONE
&& mFindInFileView.getVisibility() == GONE) {
- mFloatingActionButton.setVisibility(View.VISIBLE);
+ mFloatingActionButton.show();
} else {
- mFloatingActionButton.setVisibility(View.GONE);
+ mFloatingActionButton.hide();
}
}
@@ -123,7 +125,8 @@
if (destination.getYCoordinate() != null) {
int pageY = (int) destination.getYCoordinate().floatValue();
- Rect pageRect = mPaginationModel.getPageLocation(destination.getPageNumber());
+ Rect pageRect = mPaginationModel.getPageLocation(destination.getPageNumber(),
+ mPaginatedView.getViewArea());
int x = pageRect.left + (pageRect.width() / 2);
int y = mPaginationModel.getLookAtY(destination.getPageNumber(), pageY);
// Zoom should match the width of the page.
@@ -155,7 +158,7 @@
return;
}
- Rect pageRect = mPaginationModel.getPageLocation(pageNum);
+ Rect pageRect = mPaginationModel.getPageLocation(pageNum, mPaginatedView.getViewArea());
int x = pageRect.left + (pageRect.width() / 2);
int y = pageRect.top + (pageRect.height() / 2);
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java
index 2fb40ab..b266f91 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java
@@ -16,7 +16,7 @@
package androidx.pdf.viewer;
-import android.animation.ValueAnimator;
+import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
@@ -44,7 +44,6 @@
private final SelectionActionMode mSelectionActionMode;
private final ObservableValue<ViewState> mViewState;
- private static final int FAB_ANIMATION_DURATION = 200;
private boolean mIsPageScrollingUp;
public ZoomScrollValueObserver(@Nullable ZoomView zoomView,
@@ -68,10 +67,11 @@
@Override
public void onChange(@Nullable ZoomView.ZoomScroll oldPosition,
@Nullable ZoomView.ZoomScroll position) {
- if (mPaginatedView == null || !mPaginatedView.getPaginationModel().isInitialized()
- || position == null || mPaginatedView.getPaginationModel().getSize() == 0) {
+ if (mPaginatedView == null || !mPaginatedView.getModel().isInitialized()
+ || position == null || mPaginatedView.getModel().getSize() == 0) {
return;
}
+
mZoomView.loadPageAssets(mLayoutHandler, mViewState);
if (oldPosition.scrollY > position.scrollY) {
@@ -80,11 +80,20 @@
mIsPageScrollingUp = false;
}
+ // Stop showing context menu if there is any change in zoom or scroll, resume only when
+ // the new position is stable
+ if (mPaginatedView.getSelectionModel().selection().get() != null) {
+ mSelectionActionMode.stopActionMode();
+ if (position.stable) {
+ setUpContextMenu();
+ }
+ }
+
if (mIsAnnotationIntentResolvable && !mPaginatedView.isConfigurationChanged()) {
if (!isAnnotationButtonVisible() && position.scrollY == 0
&& mFindInFileView.getVisibility() == View.GONE) {
- editFabExpandAnimation();
+ mAnnotationButton.show();
} else if (isAnnotationButtonVisible() && mIsPageScrollingUp) {
clearAnnotationHandler();
return;
@@ -94,16 +103,7 @@
@Override
public void run() {
if (position.scrollY != 0) {
- mAnnotationButton.animate()
- .alpha(0.0f)
- .setDuration(FAB_ANIMATION_DURATION)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mAnnotationButton.setVisibility(View.GONE);
- mAnnotationButton.setAlpha(1.0f);
- }
- });
+ mAnnotationButton.hide();
}
}
});
@@ -112,38 +112,12 @@
&& position.scrollY != oldPosition.scrollY) {
mPaginatedView.setConfigurationChanged(false);
}
-
- if (position.scrollY > 0) {
- mSelectionActionMode.stopActionMode();
- }
- if (position.scrollY == oldPosition.scrollY) {
- mSelectionActionMode.resume();
- }
}
private boolean isAnnotationButtonVisible() {
return mAnnotationButton.getVisibility() == View.VISIBLE;
}
- private void editFabExpandAnimation() {
- mAnnotationButton.setScaleX(0.0f);
- mAnnotationButton.setScaleY(0.0f);
- ValueAnimator scaleAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
- scaleAnimator.setDuration(FAB_ANIMATION_DURATION);
- scaleAnimator.addUpdateListener(
- new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(@NonNull ValueAnimator animation) {
- float scale = (float) animation.getAnimatedValue();
- mAnnotationButton.setScaleX(scale);
- mAnnotationButton.setScaleY(scale);
- mAnnotationButton.setAlpha(scale);
- }
- });
- scaleAnimator.start();
- mAnnotationButton.setVisibility(View.VISIBLE);
- }
-
/** Exposing a function to clear the handler when PDFViewer Fragment is destroyed. */
public void clearAnnotationHandler() {
mAnnotationButtonHandler.removeCallbacksAndMessages(null);
@@ -152,4 +126,36 @@
public void setAnnotationIntentResolvable(boolean annotationIntentResolvable) {
mIsAnnotationIntentResolvable = annotationIntentResolvable;
}
-}
+
+ private void setUpContextMenu() {
+ // Resume the context menu if selected area is on the current viewing screen
+ if (mPaginatedView.getSelectionModel().getPage() != -1) {
+ int selectionPage = mPaginatedView.getSelectionModel().getPage();
+ int firstPageInVisibleRange =
+ mPaginatedView.getPageRangeHandler().getVisiblePages().getFirst();
+ int lastPageInVisisbleRange =
+ mPaginatedView.getPageRangeHandler().getVisiblePages().getLast();
+
+ // If selection is within the range of visible pages
+ if (selectionPage >= firstPageInVisibleRange
+ && selectionPage <= lastPageInVisisbleRange) {
+ // Start and stop coordinates in a page wrt pagination model
+ int startX = mPaginatedView.getModel().getLookAtX(selectionPage,
+ mPaginatedView.getSelectionModel().selection().get().getStart().getX());
+ int startY = mPaginatedView.getModel().getLookAtY(selectionPage,
+ mPaginatedView.getSelectionModel().selection().get().getStart().getY());
+ int stopX = mPaginatedView.getModel().getLookAtX(selectionPage,
+ mPaginatedView.getSelectionModel().selection().get().getStop().getX());
+ int stopY = mPaginatedView.getModel().getLookAtY(selectionPage,
+ mPaginatedView.getSelectionModel().selection().get().getStop().getY());
+
+ Rect currentViewArea = mPaginatedView.getViewArea();
+
+ if (currentViewArea.intersect(startX, startY, stopX, stopY)) {
+ mSelectionActionMode.resume();
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoaderCallbacksImpl.kt b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoaderCallbacksImpl.kt
index c8b00b8..b5fbe27 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoaderCallbacksImpl.kt
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfLoaderCallbacksImpl.kt
@@ -117,28 +117,27 @@
return
}
- if (selection.page >= paginatedView.paginationModel.size) {
+ if (selection.page >= paginatedView.model.size) {
layoutHandler!!.layoutPages(selection.page + 1)
return
}
val rect = selection.pageMatches.getFirstRect(selection.selected)
- val x: Int = paginatedView.paginationModel.getLookAtX(selection.page, rect.centerX())
- val y: Int = paginatedView.paginationModel.getLookAtY(selection.page, rect.centerY())
+ val x: Int = paginatedView.model.getLookAtX(selection.page, rect.centerX())
+ val y: Int = paginatedView.model.getLookAtY(selection.page, rect.centerY())
zoomView.centerAt(x.toFloat(), y.toFloat())
pageViewFactory!!
.getOrCreatePageView(
selection.page,
pageElevationInPixels,
- paginatedView.paginationModel.getPageSize(selection.page)
+ paginatedView.model.getPageSize(selection.page)
)
.setOverlay(selection.overlay)
}
private fun isPageCreated(pageNum: Int): Boolean {
- return pageNum < paginatedView.paginationModel.size &&
- paginatedView.getViewAt(pageNum) != null
+ return pageNum < paginatedView.model.size && paginatedView.getViewAt(pageNum) != null
}
private fun getPage(pageNum: Int): PageViewFactory.PageView? {
@@ -198,16 +197,12 @@
paginatedView.pageRangeHandler.maxPage = 1
if (viewState.get() != ViewState.NO_VIEW) {
if (uri != null && data.uri == uri) {
- paginatedView.paginationModel.setMaxPages(-1)
+ paginatedView.model.setMaxPages(-1)
}
- paginatedView.paginationModel.initialize(numPages)
+ paginatedView.model.initialize(numPages)
- // Add pagination model to the view
- paginatedView.model = paginatedView.paginationModel
- paginatedView.let { paginatedView.paginationModel.addObserver(it) }
-
- fastScrollView.setPaginationModel(paginatedView.paginationModel)
+ fastScrollView.setPaginationModel(paginatedView.model)
dismissPasswordDialog()
@@ -233,6 +228,9 @@
"Document not loaded but status " + status.number
)
PdfStatus.PDF_ERROR -> {
+ loadingView.showErrorView(
+ context.resources.getString(R.string.error_cannot_open_pdf)
+ )
handleError(status)
}
PdfStatus.FILE_ERROR,
@@ -246,12 +244,12 @@
override fun pageBroken(page: Int) {
if (viewState.get() != ViewState.NO_VIEW) {
- if (page < paginatedView.paginationModel.numPages) {
+ if (page < paginatedView.model.numPages) {
pageViewFactory!!
.getOrCreatePageView(
page,
pageElevationInPixels,
- paginatedView.paginationModel.getPageSize(page)
+ paginatedView.model.getPageSize(page)
)
.setFailure(context.resources.getString(R.string.error_on_page, page + 1))
// TODO: Track render error.
@@ -262,9 +260,9 @@
override fun setPageDimensions(pageNum: Int, dimensions: Dimensions) {
if (viewState.get() != ViewState.NO_VIEW) {
- paginatedView.paginationModel.addPage(pageNum, dimensions)
+ paginatedView.model.addPage(pageNum, dimensions)
- layoutHandler!!.pageLayoutReach = paginatedView.paginationModel.size
+ layoutHandler!!.pageLayoutReach = paginatedView.model.size
if (
searchModel!!.query().get() != null &&
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfPageLoader.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfPageLoader.java
index e8fe78f..441cf96 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfPageLoader.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfPageLoader.java
@@ -33,8 +33,7 @@
import androidx.pdf.service.PdfDocumentRemoteProto;
import androidx.pdf.util.TileBoard.TileInfo;
-import com.google.common.collect.ImmutableList;
-
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -650,7 +649,7 @@
protected List<GotoLink> doInBackground(PdfDocumentRemoteProto pdfDocument)
throws RemoteException {
if (TaskDenyList.sDisableLinks) {
- return ImmutableList.of();
+ return Collections.emptyList();
} else {
return pdfDocument.getPdfDocumentRemote().getPageGotoLinks(mPageNum);
}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/password/PasswordDialog.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/password/PasswordDialog.java
index 3a6e022..a994a5d 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/password/PasswordDialog.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/password/PasswordDialog.java
@@ -184,9 +184,9 @@
@Override
public void onStart() {
super.onStart();
- mTextDefaultColor = getResources().getColor(R.color.text_default);
- mTextErrorColor = getResources().getColor(R.color.text_error);
- mBlueColor = getResources().getColor(R.color.google_blue);
+ mTextDefaultColor = getResources().getColor(R.color.pdf_viewer_color_on_surface);
+ mTextErrorColor = getResources().getColor(R.color.pdf_viewer_color_error);
+ mBlueColor = getResources().getColor(R.color.pdf_viewer_color_primary);
EditText textField = (EditText) getDialog().findViewById(R.id.password);
textField.getBackground().setColorFilter(mBlueColor, PorterDuff.Mode.SRC_ATOP);
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/ZoomView.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/ZoomView.java
index d7755ef..e5a021c 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/ZoomView.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/ZoomView.java
@@ -902,7 +902,7 @@
PaginatedView paginatedView = this.findViewById(R.id.pdf_view);
ZoomScroll position = this.zoomScroll().get();
- if (position == null || !paginatedView.getPaginationModel().isInitialized()) {
+ if (position == null || !paginatedView.getModel().isInitialized()) {
return;
}
@@ -911,7 +911,7 @@
this.setStableZoom(position.zoom);
}
- paginatedView.getPaginationModel().setViewArea(this.getVisibleAreaInContentCoords());
+ paginatedView.setViewArea(this.getVisibleAreaInContentCoords());
paginatedView.refreshPageRangeInVisibleArea(position, this.getHeight());
paginatedView.handleGonePages(false);
paginatedView.loadInvisibleNearPageRange(this.getStableZoom());
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/ZoomableSelectionHandles.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/ZoomableSelectionHandles.java
index f2ca1b9..cf5a797 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/ZoomableSelectionHandles.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/widget/ZoomableSelectionHandles.java
@@ -16,6 +16,7 @@
package androidx.pdf.widget;
+import android.content.Context;
import android.content.res.Resources;
import android.view.MotionEvent;
import android.view.View;
@@ -168,16 +169,17 @@
*/
@NonNull
protected ImageView createHandle(@NonNull ViewGroup parent, boolean isStop, int id) {
- ImageView handle = new ImageView(parent.getContext());
+ Context context = parent.getContext();
+ ImageView handle = new ImageView(context);
handle.setId(id);
handle.setLayoutParams(
new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
handle.setColorFilter(
- parent.getContext().getResources().getColor(R.color.selection_handles));
+ context.getResources().getColor(R.color.pdf_viewer_selection_handles));
handle.setAlpha(HANDLE_ALPHA);
int descId = isStop ? R.string.desc_selection_stop : R.string.desc_selection_start;
- handle.setContentDescription(parent.getContext().getString(descId));
+ handle.setContentDescription(context.getString(descId));
handle.setVisibility(View.GONE);
parent.addView(handle);
handle.setOnTouchListener(mOnTouchListener);
diff --git a/pdf/pdf-viewer/src/main/res/drawable/custom_edit_text_cursor.xml b/pdf/pdf-viewer/src/main/res/drawable/custom_edit_text_cursor.xml
index d921c91..9c9ac06 100644
--- a/pdf/pdf-viewer/src/main/res/drawable/custom_edit_text_cursor.xml
+++ b/pdf/pdf-viewer/src/main/res/drawable/custom_edit_text_cursor.xml
@@ -16,5 +16,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:width="2dp" />
- <solid android:color="@color/google_blue" />
+ <solid android:color="?attr/colorPrimary" />
</shape>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/drawable/fastscroll_background.xml b/pdf/pdf-viewer/src/main/res/drawable/fastscroll_background.xml
index 922dea5..47bd7c7 100644
--- a/pdf/pdf-viewer/src/main/res/drawable/fastscroll_background.xml
+++ b/pdf/pdf-viewer/src/main/res/drawable/fastscroll_background.xml
@@ -17,5 +17,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
- <solid android:color="@android:color/white" />
+ <solid android:color="?attr/colorSurfaceContainerHigh" />
</shape>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/drawable/page_indicator_background.xml b/pdf/pdf-viewer/src/main/res/drawable/page_indicator_background.xml
index 4df08d2..fadbc1e 100644
--- a/pdf/pdf-viewer/src/main/res/drawable/page_indicator_background.xml
+++ b/pdf/pdf-viewer/src/main/res/drawable/page_indicator_background.xml
@@ -17,5 +17,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="16dp" />
- <solid android:color="@color/google_white" />
+ <solid android:color="?attr/colorSurfaceContainerHigh" />
</shape>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/drawable/selection_drag_handle_left.xml b/pdf/pdf-viewer/src/main/res/drawable/selection_drag_handle_left.xml
index 7132904..257d2c5 100644
--- a/pdf/pdf-viewer/src/main/res/drawable/selection_drag_handle_left.xml
+++ b/pdf/pdf-viewer/src/main/res/drawable/selection_drag_handle_left.xml
@@ -22,7 +22,7 @@
android:viewportWidth="44">
<path
- android:fillColor="#FFA8C7FA"
+ android:fillColor="?attr/colorPrimaryFixedDim"
android:fillType="evenOdd"
android:pathData="M33.939,9.899L33.897,23.94C33.874,31.695 27.569,38.001 19.814,38.024C12.059,38.047 5.791,31.779 5.814,24.024C5.837,16.269 12.142,9.963 19.897,9.94L33.939,9.899Z" />
diff --git a/pdf/pdf-viewer/src/main/res/drawable/selection_drag_handle_right.xml b/pdf/pdf-viewer/src/main/res/drawable/selection_drag_handle_right.xml
index 49021c7b..146f43b 100644
--- a/pdf/pdf-viewer/src/main/res/drawable/selection_drag_handle_right.xml
+++ b/pdf/pdf-viewer/src/main/res/drawable/selection_drag_handle_right.xml
@@ -18,12 +18,11 @@
android:width="44dp"
android:height="44dp"
android:autoMirrored="true"
- android:tint="#FFA8C7FA"
android:viewportHeight="44"
android:viewportWidth="44">
<path
- android:fillColor="#FFA8C7FA"
+ android:fillColor="?attr/colorPrimaryFixedDim"
android:fillType="evenOdd"
android:pathData="M9.901,9.899L9.942,23.94C9.965,31.695 16.271,38.001 24.026,38.024C31.781,38.047 38.049,31.779 38.026,24.024C38.003,16.269 31.697,9.963 23.942,9.94L9.901,9.899Z" />
diff --git a/pdf/pdf-viewer/src/main/res/drawable/shape_find_in_file.xml b/pdf/pdf-viewer/src/main/res/drawable/shape_find_in_file.xml
index 7b756b0..d827d3a 100644
--- a/pdf/pdf-viewer/src/main/res/drawable/shape_find_in_file.xml
+++ b/pdf/pdf-viewer/src/main/res/drawable/shape_find_in_file.xml
@@ -17,17 +17,17 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="@color/search_background"/>
-
- <padding
- android:left="12dp"
- android:top="12dp"
- android:bottom="12dp"/>
-
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="36dp"
- android:topRightRadius="36dp"/>
+ android:topRightRadius="36dp" />
+
+ <padding
+ android:bottom="12dp"
+ android:left="12dp"
+ android:top="12dp" />
+
+ <solid android:color="?attr/colorSurfaceContainer" />
</shape>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/drawable/shape_oval.xml b/pdf/pdf-viewer/src/main/res/drawable/shape_oval.xml
index e8b5b8e..46f1094 100644
--- a/pdf/pdf-viewer/src/main/res/drawable/shape_oval.xml
+++ b/pdf/pdf-viewer/src/main/res/drawable/shape_oval.xml
@@ -15,10 +15,10 @@
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@color/search_textbox">
+ android:color="?attr/colorSurfaceBright">
<item>
<shape android:shape="oval">
- <solid android:color="@color/search_background" />
+ <solid android:color="?attr/colorSurfaceContainer" />
</shape>
</item>
</ripple>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/drawable/shape_textbox.xml b/pdf/pdf-viewer/src/main/res/drawable/shape_textbox.xml
index 3b07c26..caf6bf9 100644
--- a/pdf/pdf-viewer/src/main/res/drawable/shape_textbox.xml
+++ b/pdf/pdf-viewer/src/main/res/drawable/shape_textbox.xml
@@ -17,7 +17,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="@color/search_textbox"/>
+ <solid android:color="?attr/colorSurfaceBright"/>
<corners
android:radius="28dp"/>
diff --git a/pdf/pdf-viewer/src/main/res/layout/dialog_password.xml b/pdf/pdf-viewer/src/main/res/layout/dialog_password.xml
index 992d5b9..66ab9f0 100644
--- a/pdf/pdf-viewer/src/main/res/layout/dialog_password.xml
+++ b/pdf/pdf-viewer/src/main/res/layout/dialog_password.xml
@@ -21,32 +21,35 @@
android:id="@+id/password_dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:padding="24dp"
- >
- <TextView android:id="@+id/label"
- style="@style/Label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/label_password_first"/>
- <EditText android:id="@+id/password"
- style="@style/TextField"
- android:inputType="textPassword"
+ android:padding="24dp">
+
+ <EditText
+ android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@id/label"
android:layout_alignParentLeft="true"
- android:selectAllOnFocus="true"
+ android:layout_below="@id/label"
android:contentDescription="@string/desc_password"
- android:textCursorDrawable="@drawable/custom_edit_text_cursor"
android:importantForAutofill="no"
+ android:inputType="textPassword"
+ android:selectAllOnFocus="true"
+ android:textCursorDrawable="@drawable/custom_edit_text_cursor"
tools:ignore="LabelFor" />
- <ImageView android:id="@+id/password_alert"
- android:src="@drawable/text_alert"
+
+ <ImageView
+ android:id="@+id/password_alert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@id/label"
android:layout_alignParentRight="true"
+ android:layout_below="@id/label"
android:contentDescription="@string/desc_password_incorrect"
- android:visibility="gone"
- />
-</RelativeLayout>
\ No newline at end of file
+ android:src="@drawable/text_alert"
+ android:visibility="gone" />
+
+ <TextView
+ android:id="@+id/label"
+ android:textColor="?attr/colorPrimary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_password_first" />
+</RelativeLayout>
diff --git a/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml b/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
index 0e5e70a..176e323 100644
--- a/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
+++ b/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
@@ -17,7 +17,6 @@
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="@+id/search_container"
@@ -33,8 +32,8 @@
android:layout_height="48dp"
android:layout_weight="1"
android:hint="@string/hint_find"
- android:textColor="@color/search_textColor"
- android:textColorHint="@color/search_texthint"
+ android:textColor="?attr/colorOnSurface"
+ android:textColorHint="?attr/colorOutline"
android:paddingLeft="16dp"
android:imeOptions="actionSearch"
android:inputType="textFilter"
@@ -50,7 +49,7 @@
android:layout_height="wrap_content"
android:layout_alignEnd="@+id/query_box"
android:paddingRight="10dp"
- android:textColor="@color/search_count">
+ android:textColor="?attr/colorOnSurfaceVariant">
</TextView>
</LinearLayout>
@@ -60,7 +59,7 @@
android:layout_height="34dp"
android:background="@drawable/shape_oval"
android:src="@drawable/keyboard_up"
- app:tint="@color/search_prev_button"
+ app:tint="?attr/colorOnSurfaceVariant"
android:cropToPadding="true"
android:padding="3dp"
android:scaleType="centerInside"
@@ -73,7 +72,7 @@
android:layout_height="34dp"
android:background="@drawable/shape_oval"
android:src="@drawable/keyboard_down"
- app:tint="@color/search_next_button"
+ app:tint="?attr/colorOnSurfaceVariant"
android:cropToPadding="true"
android:padding="3dp"
android:scaleType="centerInside"
@@ -85,7 +84,7 @@
android:layout_height="34dp"
android:background="@drawable/shape_oval"
android:src="@drawable/close_button"
- app:tint="@color/search_close_button"
+ app:tint="?attr/colorOnSurfaceVariant"
android:cropToPadding="true"
android:padding="5dp"
android:scaleType="centerInside"
diff --git a/pdf/pdf-viewer/src/main/res/layout/loading_animation.xml b/pdf/pdf-viewer/src/main/res/layout/loading_animation.xml
index bb85a0e..fe9036d 100644
--- a/pdf/pdf-viewer/src/main/res/layout/loading_animation.xml
+++ b/pdf/pdf-viewer/src/main/res/layout/loading_animation.xml
@@ -26,7 +26,6 @@
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
- app:trackColor="@color/material_on_background_emphasis_high_type"
- />
+ app:trackColor="?attr/colorSecondaryContainer" />
</LinearLayout>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/layout/loading_view.xml b/pdf/pdf-viewer/src/main/res/layout/loading_view.xml
index 35c28a2..d52f385 100644
--- a/pdf/pdf-viewer/src/main/res/layout/loading_view.xml
+++ b/pdf/pdf-viewer/src/main/res/layout/loading_view.xml
@@ -32,6 +32,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
- android:textColor="@android:color/holo_red_dark"
+ android:textColor= "?attr/colorOnSurface"
+ android:textAppearance="?attr/textAppearanceBodyMedium"
+ android:gravity= "center"
android:visibility="gone"/>
</LinearLayout>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/layout/page_indicator.xml b/pdf/pdf-viewer/src/main/res/layout/page_indicator.xml
index 78b5580..6de07dd 100644
--- a/pdf/pdf-viewer/src/main/res/layout/page_indicator.xml
+++ b/pdf/pdf-viewer/src/main/res/layout/page_indicator.xml
@@ -27,7 +27,7 @@
android:gravity="center"
android:paddingLeft="12dp"
android:paddingRight="12dp"
- android:textColor="@color/google_grey"
+ android:textColor="?attr/colorOnSurface"
android:textSize="12sp"
tools:ignore="RtlHardcoded"
tools:text="3 of 20"
diff --git a/pdf/pdf-viewer/src/main/res/layout/pdf_view_container.xml b/pdf/pdf-viewer/src/main/res/layout/pdf_view_container.xml
index a5f3248..6f962e1 100644
--- a/pdf/pdf-viewer/src/main/res/layout/pdf_view_container.xml
+++ b/pdf/pdf-viewer/src/main/res/layout/pdf_view_container.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,10 +17,10 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
- <include layout="@layout/file_viewer_pdf"/>
- <include layout="@layout/loading_animation"/>
+ <include layout="@layout/file_viewer_pdf" />
+
+ <include layout="@layout/loading_animation" />
</FrameLayout>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/layout/search.xml b/pdf/pdf-viewer/src/main/res/layout/search.xml
index 712c0d2..a833ba5 100644
--- a/pdf/pdf-viewer/src/main/res/layout/search.xml
+++ b/pdf/pdf-viewer/src/main/res/layout/search.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,14 +14,12 @@
limitations under the License.
-->
-<androidx.pdf.find.FindInFileView
- xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.pdf.find.FindInFileView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="72dp"
+ android:layout_gravity="bottom"
android:background="@drawable/shape_find_in_file"
android:orientation="horizontal"
- android:visibility="gone"
- android:layout_gravity="bottom"
- >
+ android:visibility="gone">
</androidx.pdf.find.FindInFileView>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/values-af/strings.xml b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
index 19b1121c..3efa5d2 100644
--- a/pdf/pdf-viewer/src/main/res/values-af/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Lêertipe"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Hierdie lêer is beskerm"</string>
<string name="label_password_first" msgid="4456258714097111908">"Wagwoord"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Wagwoord verkeerd"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoem <xliff:g id="FIRST">%1$d</xliff:g> persent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Gaan na bladsy <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"bladsy <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Kies teks om opmerking te plaas"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Tik op ’n area om opmerkings te maak"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Kanselleer"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Kan nie PDF vertoon nie (<xliff:g id="TITLE">%1$s</xliff:g> is ’n ongeldige formaat)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Kan nie bladsy <xliff:g id="PAGE">%1$d</xliff:g> vertoon nie (lêerfout)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Kan nie annotasiemodus vir hierdie item laai nie."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Skakel: webblad by <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Skakel: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-posadres: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"bladsy <xliff:g id="FIRST">%1$d</xliff:g> tot <xliff:g id="LAST">%2$d</xliff:g> van <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Prent: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Soek in lêer"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Geen passende resultate nie."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Vorige"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Volgende"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Maak toe"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Wysig lêer"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Voer wagwoord in om te ontsluit"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Kon nie die lêer oopmaak nie. Moontlike toestemmingkwessie?"</string>
<string name="page_broken" msgid="2968770793669433462">"Bladsy is vir die PDF-dokument gebreek"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Onvoldoende data om die PDF-dokument te verwerk"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-am/strings.xml b/pdf/pdf-viewer/src/main/res/values-am/strings.xml
index 59e6e04..a2679692 100644
--- a/pdf/pdf-viewer/src/main/res/values-am/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-am/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"የፋይል አይነት"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ይህ ፋይል የተጠበቀ ነው"</string>
<string name="label_password_first" msgid="4456258714097111908">"የይለፍ ቃል"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"የይለፍ ቃል ትክክል አይደለም"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> በመቶ ያጉሉ"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"ወደ ገጽ <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> ይሂዱ"</string>
<string name="desc_page" msgid="5684226167093594168">"ገፅ <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"አስተያየትዎን ለማስቀመጥ ጽሑፍን ይምረጡ"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"አስተያየት ለመስጠት አንድ አካባቢ ላይ መታ ያድርጉ"</string>
- <string name="action_cancel" msgid="5494417739210197522">"ይቅር"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF ማሳየት አልተቻለም (<xliff:g id="TITLE">%1$s</xliff:g> ልክ ያልኾነ ቅርጸት ያለው ነው)"</string>
<string name="error_on_page" msgid="1592475819957182385">"ገጽ <xliff:g id="PAGE">%1$d</xliff:g>ን ማሳየት አልተቻለም (የፋይል ስሕተት)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"ለዚህ ንጥል የማብራሪያ ሁነታን መጫን አልተቻለም።"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"አገናኝ፦ <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> ላይ ያለ ድረ-ገጽ"</string>
<string name="desc_web_link" msgid="2776023299237058419">"አገናኝ፦ <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ኢሜይል፦ <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="FIRST">%1$d</xliff:g> እስከ <xliff:g id="LAST">%2$d</xliff:g> ገጾች ከ<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"ምስል፦ <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ፋይል ውስጥ ያግኙ"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"ምንም ተመሳሳዮች አልተገኙም።"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"ቀዳሚ"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"ቀጣይ"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"ዝጋ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ፋይል አርትዕ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ለመክፈት የይለፍ ቃል ያስገቡ"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ፋይሉን መክፈት አልተሳካም። የፈቃድ ችግር ሊሆን ይችላል?"</string>
<string name="page_broken" msgid="2968770793669433462">"ለPDF ሰነዱ ገፅ ተበላሽቷል"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ሰነዱን ለማሰናዳት በቂ ያልሆነ ውሂብ"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ar/strings.xml b/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
index 8f89292..eae5948 100644
--- a/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"نوع الملف"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"هذا الملف محمي"</string>
<string name="label_password_first" msgid="4456258714097111908">"كلمة المرور"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"كلمة المرور غير صحيحة"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"التكبير أو التصغير بنسبة <xliff:g id="FIRST">%1$d</xliff:g> في المئة"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"الانتقال إلى الصفحة <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"الصفحة <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"حدِّد نصًا لكتابة تعليقك"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"انقر على منطقة للتعليق عليها"</string>
- <string name="action_cancel" msgid="5494417739210197522">"إلغاء"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"يتعذَّر عرض ملف PDF (تنسيق الملف \"<xliff:g id="TITLE">%1$s</xliff:g>\" غير صالح)"</string>
<string name="error_on_page" msgid="1592475819957182385">"يتعذَّر عرض الصفحة <xliff:g id="PAGE">%1$d</xliff:g> (خطأ في الملف)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"يتعذَّر تحميل وضع التعليقات التوضيحية لهذا العنصر."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"الرابط: صفحة ويب في <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"الرابط: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"البريد الإلكتروني: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"الصفحات من <xliff:g id="FIRST">%1$d</xliff:g> إلى <xliff:g id="LAST">%2$d</xliff:g> من إجمالي <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"صورة: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"البحث في الملف"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"لم يتم العثور على نتائج مطابِقة."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"السابق"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"التالي"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"إغلاق"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> من أصل <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"تعديل الملف"</string>
<string name="password_not_entered" msgid="8875370870743585303">"يجب إدخال كلمة المرور لفتح القفل"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"تعذّر فتح الملف. هل توجد مشكلة محتملة في الأذونات؟"</string>
<string name="page_broken" msgid="2968770793669433462">"تعذّر تحميل صفحة من مستند PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"البيانات غير كافية لمعالجة مستند PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-as/strings.xml b/pdf/pdf-viewer/src/main/res/values-as/strings.xml
index 3de154e..f9c9ef7 100644
--- a/pdf/pdf-viewer/src/main/res/values-as/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-as/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ফাইলৰ প্রকাৰ"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"এই ফাইলটো সুৰক্ষিত"</string>
<string name="label_password_first" msgid="4456258714097111908">"পাছৱৰ্ড"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"পাছৱৰ্ড ভুল হৈছে"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> শতাংশ জুম কৰক"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"পৃষ্ঠা <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>লৈ যাওক"</string>
<string name="desc_page" msgid="5684226167093594168">"পৃষ্ঠা <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"আপোনাৰ মন্তব্য দিবলৈ পাঠ বাছনি কৰক"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"মন্তব্য দিবলৈ এটুকুৰা ঠাইত টিপক"</string>
- <string name="action_cancel" msgid="5494417739210197522">"বাতিল কৰক"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF দেখুৱাব নোৱাৰি (<xliff:g id="TITLE">%1$s</xliff:g>ৰ ফৰ্মেটটো মান্য নহয়)"</string>
<string name="error_on_page" msgid="1592475819957182385">"পৃষ্ঠা <xliff:g id="PAGE">%1$d</xliff:g> দেখুৱাব নোৱাৰি (ফাইলৰ আঁসোৱাহ)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"এই বস্তুটোৰ বাবে এন’টেশ্বন ম’ড ল’ড কৰিব নোৱাৰি।"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"লিংক: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>ত থকা ৱেবপৃষ্ঠা"</string>
<string name="desc_web_link" msgid="2776023299237058419">"লিংক: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ইমেইল: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g> খন পৃষ্ঠাৰ <xliff:g id="FIRST">%1$d</xliff:g>ৰ পৰা <xliff:g id="LAST">%2$d</xliff:g>লৈ থকা পৃষ্ঠাসমূহ"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"প্ৰতিচ্ছবি: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ফাইলত বিচাৰক"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"কোনো মিল পোৱা নগ’ল।"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"পূৰ্বৱৰ্তী"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"পৰৱৰ্তী"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"বন্ধ কৰক"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ফাইল সম্পাদনা কৰক"</string>
<string name="password_not_entered" msgid="8875370870743585303">"আনলক কৰিবলৈ পাছৱৰ্ড দিয়ক"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ফাইলটো খুলিব পৰা নগ’ল। সম্ভাব্য অনুমতি সম্পৰ্কীয় সমস্যা?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF নথিৰ বাবে পৃষ্ঠাখন বিসংগতিপূৰ্ণ"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF নথিখন প্ৰক্ৰিয়াকৰণ কৰিবলৈ অপৰ্যাপ্ত ডেটা"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-az/strings.xml b/pdf/pdf-viewer/src/main/res/values-az/strings.xml
index 4293921..6ef1751 100644
--- a/pdf/pdf-viewer/src/main/res/values-az/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-az/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Fayl növü"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Bu fayl qorunur"</string>
<string name="label_password_first" msgid="4456258714097111908">"Parol"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Parol səhvdir"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> faiz zum"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> səhifəsinə keçin"</string>
<string name="desc_page" msgid="5684226167093594168">"səhifə <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Şərhi yerləşdirmək üçün mətn seçin"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Şərh yazmaq üçün sahəyə toxunun"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Ləğv edin"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF göstərilmir (<xliff:g id="TITLE">%1$s</xliff:g> yanlış formatdadır)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g> saylı səhifə göstərilmir (fayl xətası)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Bu element üçün annotasiya rejimi yüklənmir."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> ünvanında veb-səhifə"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-poçt: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g> səhifənin <xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> səhifələri"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Şəkil: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Faylda tapın"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Uyğunluq tapılmadı."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Əvvəlki"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Növbəti"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Bağlayın"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Faylı redaktə edin"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Kiliddən çıxarmaq üçün parol daxil edin"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Fayl açılmadı. İcazə problemi var?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF sənədi üçün səhifədə xəta var"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF sənədini emal etmək üçün kifayət qədər data yoxdur"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml b/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
index 2723618..f259654 100644
--- a/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Tip fajla"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Ovaj fajl je zaštićen"</string>
<string name="label_password_first" msgid="4456258714097111908">"Lozinka"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Lozinka je netačna"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"procenat zuma: <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Idi na stranicu <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g>. stranica"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Izaberite tekst za postavljanje komentara"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Dodirnite oblast za komentarisanje"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Otkaži"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF ne može da se prikaže (<xliff:g id="TITLE">%1$s</xliff:g> ima nevažeći format)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Stranica <xliff:g id="PAGE">%1$d</xliff:g> ne može da se prikaže (greška u fajlu)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Učitavanje režima napomena za ovu stavku nije uspelo."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: veb-stranica na <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Imejl: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"stranice <xliff:g id="FIRST">%1$d</xliff:g>. do <xliff:g id="LAST">%2$d</xliff:g>. od <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Slika: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Pronađite u fajlu"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nije pronađeno nijedno podudaranje."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Prethodno"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Dalje"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Zatvori"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Izmeni fajl"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Unesite lozinku za otključavanje"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Otvaranje fajla nije uspelo. Možda postoje problemi sa dozvolom?"</string>
<string name="page_broken" msgid="2968770793669433462">"Neispravna stranica za PDF dokument"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nedovoljno podataka za obradu PDF dokumenta"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-be/strings.xml b/pdf/pdf-viewer/src/main/res/values-be/strings.xml
index 179a2e7..c951b2c 100644
--- a/pdf/pdf-viewer/src/main/res/values-be/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-be/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Тып файла"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Гэты файл абаронены"</string>
<string name="label_password_first" msgid="4456258714097111908">"Пароль"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Няправільны пароль"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"маштаб <xliff:g id="FIRST">%1$d</xliff:g>%%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"На старонку <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"старонка <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Выберыце тэкст, каб змясціць свой каментарый"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Націсніце там, дзе будзеце каментаваць"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Скасаваць"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Не ўдаецца паказаць PDF-файл \"<xliff:g id="TITLE">%1$s</xliff:g>\" (няправільны фармат)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Немагчыма адлюстраваць старонку <xliff:g id="PAGE">%1$d</xliff:g> (памылка файла)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Не ўдаецца загрузіць рэжым анатацый для гэтага элемента."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Спасылка: вэб-старонка <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Спасылка: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Адрас электроннай пошты: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"старонкі з <xliff:g id="FIRST">%1$d</xliff:g> па <xliff:g id="LAST">%2$d</xliff:g> (усяго <xliff:g id="TOTAL">%3$d</xliff:g>)"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Відарыс: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Знайсці ў файле"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Супадзенні не знойдзены."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Назад"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Далей"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Закрыць"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> з <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Рэдагаваць файл"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Увядзіце пароль для разблакіроўкі"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Не ўдалося адкрыць файл. Магчыма, праблема з дазволам?"</string>
<string name="page_broken" msgid="2968770793669433462">"Старонка дакумента PDF пашкоджана"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Не хапае даных для апрацоўкі дакумента PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-bg/strings.xml b/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
index 67b6be7..004be66 100644
--- a/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Файлов тип"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Този файл е защитен"</string>
<string name="label_password_first" msgid="4456258714097111908">"Парола"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Неправилна парола"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"Процент на промяна на мащаба: <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Към страница <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"страница <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Изберете текст, за да поставите коментара си"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Докоснете област, която да коментирате"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Отказ"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF файлът <xliff:g id="TITLE">%1$s</xliff:g> не може да се покаже (форматът е невалиден)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Страница <xliff:g id="PAGE">%1$d</xliff:g> не може да се покаже (грешка във файла)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Режимът за пояснения не може да се зареди за този елемент."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Връзка: уеб страница от <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Връзка: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Имейл адрес: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"страници <xliff:g id="FIRST">%1$d</xliff:g> до <xliff:g id="LAST">%2$d</xliff:g> от <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Изображение: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Търсете във файла"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Няма намерени съответствия."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Назад"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Напред"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Затваряне"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Редактиране на файла"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Въведете паролата, за да отключите"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"Файлът не бе отворен. Възможно е да има проблем с разрешенията."</string>
<string name="page_broken" msgid="2968770793669433462">"Невалидна страница в PDF документа"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Няма достатъчно данни за обработването на PDF документа"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF файлът не може да се отвори"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-bn/strings.xml b/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
index 7b5287a..937be09 100644
--- a/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ফাইলের ধরন"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"এই ফাইল সুরক্ষিত আছে"</string>
<string name="label_password_first" msgid="4456258714097111908">"পাসওয়ার্ড"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"পাসওয়ার্ড সঠিক নয়"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> শতাংশ জুম করুন"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> নম্বর পৃষ্ঠায় যান"</string>
<string name="desc_page" msgid="5684226167093594168">"পৃষ্ঠা <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"আপনার কমেন্ট করতে টেক্সট বেছে নিন"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"কমেন্ট করতে কোনও একটি অংশে ট্যাপ করুন"</string>
- <string name="action_cancel" msgid="5494417739210197522">"বাতিল করুন"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"পিডিএফ দেখানো যাবে না (<xliff:g id="TITLE">%1$s</xliff:g> ভুল ফর্ম্যাটে আছে)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g> পৃষ্ঠা দেখানো যাবে না (ফাইলে সমস্যা)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"এই আইটেমের জন্য অ্যানোটেশন মোড লোড করা যায়নি।"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"লিঙ্ক: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>-এ ওয়েবপেজ"</string>
<string name="desc_web_link" msgid="2776023299237058419">"লিঙ্ক: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ইমেল: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g>টির মধ্যে <xliff:g id="FIRST">%1$d</xliff:g> থেকে <xliff:g id="LAST">%2$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"ছবি: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ফাইলে খুঁজুন"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"কোনও মিল খুঁজে পাওয়া যায়নি।"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"পূর্ববর্তী"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"পরবর্তী"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"বন্ধ করুন"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ফাইল এডিট করুন"</string>
<string name="password_not_entered" msgid="8875370870743585303">"আনলক করতে পাসওয়ার্ড লিখুন"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ফাইল খোলা যায়নি। অনুমতি সংক্রান্ত সমস্যার কারণে এটি হতে পারে?"</string>
<string name="page_broken" msgid="2968770793669433462">"পিডিএফ ডকুমেন্টের ক্ষেত্রে পৃষ্ঠা ভেঙে গেছে"</string>
<string name="needs_more_data" msgid="3520133467908240802">"পিডিএফ ডকুমেন্ট প্রসেস করার জন্য যথেষ্ট ডেটা নেই"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-bs/strings.xml b/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
index 4944bfa..cd49b7a 100644
--- a/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Vrsta fajla"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Fajl je zaštićen"</string>
<string name="label_password_first" msgid="4456258714097111908">"Lozinka"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Pogrešna lozinka"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zumiranje <xliff:g id="FIRST">%1$d</xliff:g> posto"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Odlazak na <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>. stranicu"</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g>. stranica"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Odaberite tekst da unesete komentar"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Dodirnite područje koje ćete komentirati"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Otkaži"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Nije moguće prikazati PDF (fajl <xliff:g id="TITLE">%1$s</xliff:g> ima nevažeći format)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Nije moguće prikazati <xliff:g id="PAGE">%1$d</xliff:g>. stranicu (greška fajla)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Nije moguće učitati način rada za bilješke za ovu stavku."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: web stranica na <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Adresa e-pošte: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"od <xliff:g id="FIRST">%1$d</xliff:g>. do <xliff:g id="LAST">%2$d</xliff:g>. stranice od <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Slika: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Pronađi u fajlu"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nije pronađeno nijedno podudaranje."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Nazad"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Naprijed"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Zatvaranje"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Uredite fajl"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Unesite lozinku da otključate fajl"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Otvaranje fajla nije uspjelo. Možda postoji problem s odobrenjem?"</string>
<string name="page_broken" msgid="2968770793669433462">"Stranica je prelomljena za PDF dokument"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nema dovoljno podataka za obradu PDF dokumenta"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ca/strings.xml b/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
index b8b2b4c..4f3deac 100644
--- a/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Tipus de fitxer"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Aquest fitxer està protegit"</string>
<string name="label_password_first" msgid="4456258714097111908">"Contrasenya"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Contrasenya incorrecta"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> per cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ves a la pàgina <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"pàgina <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Selecciona text per escriure un comentari"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Toca una zona per comentar-la"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancel·la"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"No es pot mostrar el PDF (<xliff:g id="TITLE">%1$s</xliff:g> no és un format vàlid)"</string>
<string name="error_on_page" msgid="1592475819957182385">"No es pot mostrar la pàgina <xliff:g id="PAGE">%1$d</xliff:g> (error del fitxer)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"No es pot carregar el mode d\'anotació per a aquest element."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Enllaç: pàgina web a <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Enllaç: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Adreça electrònica: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"pàgines <xliff:g id="FIRST">%1$d</xliff:g> a <xliff:g id="LAST">%2$d</xliff:g> de <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imatge: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Cerca al fitxer"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"No s\'han trobat coincidències."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Anterior"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Següent"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Tanca"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Edita el fitxer"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Introdueix la contrasenya per desbloquejar-lo"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"No s\'ha pogut obrir el fitxer. És possible que hi hagi un problema de permisos?"</string>
<string name="page_broken" msgid="2968770793669433462">"La pàgina no funciona per al document PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Les dades són insuficients per processar el document PDF"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"No es pot obrir el fitxer PDF"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-cs/strings.xml b/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
index 395aa47..cdc4b65 100644
--- a/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Typ souboru"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Tento soubor je chráněn"</string>
<string name="label_password_first" msgid="4456258714097111908">"Heslo"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Nesprávné heslo"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"lupa <xliff:g id="FIRST">%1$d</xliff:g> procent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Přejít na stránku <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"stránka <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Vyberte text k umístění komentáře"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Klepněte na oblast k okomentování"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Zrušit"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF nelze zobrazit (<xliff:g id="TITLE">%1$s</xliff:g> má neplatný formát)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Stránku <xliff:g id="PAGE">%1$d</xliff:g> nelze zobrazit (chyba souboru)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Pro tuto položku nelze načíst režim poznámek."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Odkaz: stránka na webu <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Odkaz: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E‑mail: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"stránky <xliff:g id="FIRST">%1$d</xliff:g> až <xliff:g id="LAST">%2$d</xliff:g> z <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Obrázek: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Najít v souboru"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nebyly nalezeny žádné shody."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Předchozí"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Další"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Zavřít"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Upravit soubor"</string>
<string name="password_not_entered" msgid="8875370870743585303">"K odemknutí zadejte heslo"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Soubor se nepodařilo otevřít. Může se jednat o problém s oprávněním."</string>
<string name="page_broken" msgid="2968770793669433462">"Dokument PDF obsahuje poškozenou stránku"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nedostatek dat ke zpracování dokumentu PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-da/strings.xml b/pdf/pdf-viewer/src/main/res/values-da/strings.xml
index 9b95de8..5bcd02c 100644
--- a/pdf/pdf-viewer/src/main/res/values-da/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-da/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Filtype"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Denne fil er beskyttet"</string>
<string name="label_password_first" msgid="4456258714097111908">"Adgangskode"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Adgangskoden er forkert"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> %%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Gå til side <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"side <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Markér tekst for at indsætte din kommentar"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Tryk på et område, du vil kommentere"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Annuller"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF kan ikke vises (<xliff:g id="TITLE">%1$s</xliff:g> er i et ugyldigt format)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Side <xliff:g id="PAGE">%1$d</xliff:g> kan ikke vises (filfejl)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Annoteringstilstanden for dette element kan ikke indlæses."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: webside på <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Mailadresse: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"side <xliff:g id="FIRST">%1$d</xliff:g> til <xliff:g id="LAST">%2$d</xliff:g> af <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Billede: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Søg i fil"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Der blev ikke fundet noget match."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Forrige"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Næste"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Luk"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Rediger fil"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Angiv adgangskode for at låse op"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Filen kunne ikke åbnes Mon der er et problem med tilladelserne?"</string>
<string name="page_broken" msgid="2968770793669433462">"Siden er ødelagt for PDF-dokumentet"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Der er ikke nok data til at behandle PDF-dokumentet"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-de/strings.xml b/pdf/pdf-viewer/src/main/res/values-de/strings.xml
index 1ac353e..72b1096 100644
--- a/pdf/pdf-viewer/src/main/res/values-de/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-de/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Dateityp"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Diese Datei ist geschützt"</string>
<string name="label_password_first" msgid="4456258714097111908">"Passwort"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Falsches Passwort"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"Zoom: <xliff:g id="FIRST">%1$d</xliff:g> %%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Gehe zu Seite <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"Seite <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Zum Einfügen des Kommentars Text auswählen"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Auf Bereich tippen, um zu kommentieren"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Abbrechen"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Anzeige von PDF nicht möglich („<xliff:g id="TITLE">%1$s</xliff:g>“ hat ungültiges Dateiformat)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Anzeige von Seite <xliff:g id="PAGE">%1$d</xliff:g> nicht möglich (Dateifehler)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Anmerkungsmodus für dieses Element kann nicht geladen werden."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: Webseite unter <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-Mail-Adresse: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"Seiten <xliff:g id="FIRST">%1$d</xliff:g> bis <xliff:g id="LAST">%2$d</xliff:g> von <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Bild: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"In Datei suchen"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Keine Übereinstimmungen gefunden."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Zurück"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Weiter"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Schließen"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Datei bearbeiten"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Gib zum Entsperren ein Passwort ein"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Datei konnte nicht geöffnet werden. Möglicherweise ein Berechtigungsproblem?"</string>
<string name="page_broken" msgid="2968770793669433462">"Seite für PDF-Dokument ist fehlerhaft"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Keine ausreichenden Daten, um das PDF-Dokument zu verarbeiten"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-el/strings.xml b/pdf/pdf-viewer/src/main/res/values-el/strings.xml
index 74b6dfa..ada33d8 100644
--- a/pdf/pdf-viewer/src/main/res/values-el/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-el/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Τύπος αρχείου"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Αυτό το αρχείο είναι προστατευμένο"</string>
<string name="label_password_first" msgid="4456258714097111908">"Κωδικός πρόσβασης"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Εσφαλμένος κωδικός πρόσβασης"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"εστίαση <xliff:g id="FIRST">%1$d</xliff:g> τοις εκατό"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Μετάβαση στη σελίδα <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"σελίδα <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Επιλέξτε κείμενο για να τοποθετήσετε σχόλιο"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Πατήστε μια περιοχή για να σχολιάσετε"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Ακύρωση"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Δεν είναι δυνατή η προβολή του PDF (μη έγκυρη μορφή του <xliff:g id="TITLE">%1$s</xliff:g>)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Δεν είναι δυνατή η προβολή της σελίδας <xliff:g id="PAGE">%1$d</xliff:g> (σφάλμα αρχείου)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Δεν είναι δυνατή η φόρτωση της λειτουργίας σχολιασμού για αυτό το στοιχείο."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Σύνδεσμος: ιστοσελίδα στη διεύθυνση <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Σύνδεσμος: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Διεύθυνση ηλεκτρονικού ταχυδρομείου: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"σελίδες <xliff:g id="FIRST">%1$d</xliff:g> έως <xliff:g id="LAST">%2$d</xliff:g> από <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Εικόνα: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Εύρεση σε αρχείο"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Δεν εντοπίστηκαν αντιστοιχίσεις."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Προηγούμενο"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Επόμενο"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Κλείσιμο"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Επεξεργασία αρχείου"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Εισαγάγετε τον κωδικό πρόσβασης για ξεκλείδωμα"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Δεν ήταν δυνατό το άνοιγμα του αρχείου. Μήπως υπάρχει κάποιο πρόβλημα με την άδεια;"</string>
<string name="page_broken" msgid="2968770793669433462">"Δεν ήταν δυνατή η φόρτωση του εγγράφου PDF από τη σελίδα"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Μη επαρκή δεδομένα για την επεξεργασία του εγγράφου PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
index 609570b..2f9e726 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"File type"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"This file is protected"</string>
<string name="label_password_first" msgid="4456258714097111908">"Password"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Password incorrect"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> per cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Go to page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"page <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Select text to place your comment"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Tap an area to comment on it"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancel"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Cannot display PDF (<xliff:g id="TITLE">%1$s</xliff:g> is of invalid format)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Cannot display page <xliff:g id="PAGE">%1$d</xliff:g> (file error)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Can\'t load annotation mode for this item."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: web page at <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> to <xliff:g id="LAST">%2$d</xliff:g> of <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Find in file"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"No matches found."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Previous"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Next"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Close"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Failed to open the file. Possible permission issue?"</string>
<string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml
index 3be5a4f..66f8a08 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"File type"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"This file is protected"</string>
<string name="label_password_first" msgid="4456258714097111908">"Password"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Password incorrect"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> percent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Go to page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"page <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Select text to place your comment"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Tap an area to comment on"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancel"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Cannot display PDF (<xliff:g id="TITLE">%1$s</xliff:g> is of invalid format)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Cannot display page <xliff:g id="PAGE">%1$d</xliff:g> (file error)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Can\'t load annotation mode for this item."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: webpage at <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> to <xliff:g id="LAST">%2$d</xliff:g> of <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Find in file"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"No matches found."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Previous"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Next"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Close"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"Failed to open the file. Possible permission issue?"</string>
<string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"Can\'t open PDF file"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
index 609570b..2f9e726 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"File type"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"This file is protected"</string>
<string name="label_password_first" msgid="4456258714097111908">"Password"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Password incorrect"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> per cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Go to page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"page <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Select text to place your comment"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Tap an area to comment on it"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancel"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Cannot display PDF (<xliff:g id="TITLE">%1$s</xliff:g> is of invalid format)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Cannot display page <xliff:g id="PAGE">%1$d</xliff:g> (file error)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Can\'t load annotation mode for this item."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: web page at <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> to <xliff:g id="LAST">%2$d</xliff:g> of <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Find in file"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"No matches found."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Previous"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Next"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Close"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Failed to open the file. Possible permission issue?"</string>
<string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
index 609570b..2f9e726 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"File type"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"This file is protected"</string>
<string name="label_password_first" msgid="4456258714097111908">"Password"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Password incorrect"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> per cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Go to page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"page <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Select text to place your comment"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Tap an area to comment on it"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancel"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Cannot display PDF (<xliff:g id="TITLE">%1$s</xliff:g> is of invalid format)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Cannot display page <xliff:g id="PAGE">%1$d</xliff:g> (file error)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Can\'t load annotation mode for this item."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: web page at <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> to <xliff:g id="LAST">%2$d</xliff:g> of <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Find in file"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"No matches found."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Previous"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Next"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Close"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Failed to open the file. Possible permission issue?"</string>
<string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rXC/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rXC/strings.xml
index c63cb2d..f0cfb06 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rXC/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rXC/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"File type"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"This file is protected"</string>
<string name="label_password_first" msgid="4456258714097111908">"Password"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Password incorrect"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> percent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Go to page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"page <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Select text to place your comment"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Tap an area to comment on"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancel"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Cannot display PDF (<xliff:g id="TITLE">%1$s</xliff:g> is of invalid format)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Cannot display page <xliff:g id="PAGE">%1$d</xliff:g> (file error)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Can\'t load annotation mode for this item."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: webpage at <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> to <xliff:g id="LAST">%2$d</xliff:g> of <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Find in file"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"No matches found."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Previous"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Next"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Close"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"Failed to open the file. Possible permission issue?"</string>
<string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"Can\'t open PDF file"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml b/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
index dbe8fae..388c071 100644
--- a/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Tipo de archivo"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Este archivo está protegido"</string>
<string name="label_password_first" msgid="4456258714097111908">"Contraseña"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"La contraseña es incorrecta"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> por ciento"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ir a la página <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"página <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Selecciona texto para colocar tu comentario"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Presiona un área para comentar"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancelar"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"No se puede mostrar el PDF (<xliff:g id="TITLE">%1$s</xliff:g> tiene un formato no válido)"</string>
<string name="error_on_page" msgid="1592475819957182385">"No se puede mostrar la página <xliff:g id="PAGE">%1$d</xliff:g> (error de archivo)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"No se puede cargar el modo de anotación para este elemento."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Vínculo: página web en <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Vínculo: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Correo electrónico: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"páginas <xliff:g id="FIRST">%1$d</xliff:g> a <xliff:g id="LAST">%2$d</xliff:g> de <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imagen: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Buscar en el archivo"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"No hay coincidencias."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Anterior"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Siguiente"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Cerrar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar el archivo"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Ingresa la contraseña para desbloquear"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"No se pudo abrir el archivo. ¿Puede que se deba a un problema de permisos?"</string>
<string name="page_broken" msgid="2968770793669433462">"La página no funciona para el documento PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"No hay datos suficientes para procesar el documento PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-es/strings.xml b/pdf/pdf-viewer/src/main/res/values-es/strings.xml
index fa7f116..b1aca5f 100644
--- a/pdf/pdf-viewer/src/main/res/values-es/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-es/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Tipo de archivo"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Este archivo está protegido"</string>
<string name="label_password_first" msgid="4456258714097111908">"Contraseña"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Contraseña incorrecta"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> por ciento"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ir a la página <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"página <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Selecciona el texto para añadir tu comentario"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Toca una zona para dejar un comentario"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancelar"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"No se puede mostrar el PDF (el formato de <xliff:g id="TITLE">%1$s</xliff:g> no es válido)"</string>
<string name="error_on_page" msgid="1592475819957182385">"No se puede mostrar la página <xliff:g id="PAGE">%1$d</xliff:g> (error de archivo)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"No se puede cargar el modo de anotación en este elemento."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Enlace: página web en <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Enlace: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Correo: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"páginas <xliff:g id="FIRST">%1$d</xliff:g> a <xliff:g id="LAST">%2$d</xliff:g> de <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imagen: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Buscar en el archivo"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"No se han encontrado coincidencias."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Anterior"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Siguiente"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Cerrar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar archivo"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Introduce la contraseña para desbloquear"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"No se ha podido abrir el archivo. ¿Puede que se deba a un problema de permisos?"</string>
<string name="page_broken" msgid="2968770793669433462">"La página no funciona para el documento PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Datos insuficientes para procesar el documento PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-et/strings.xml b/pdf/pdf-viewer/src/main/res/values-et/strings.xml
index 4b085c6..d640ef5 100644
--- a/pdf/pdf-viewer/src/main/res/values-et/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-et/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Faili tüüp"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Fail on kaitstud"</string>
<string name="label_password_first" msgid="4456258714097111908">"Parool"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Parool on vale"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"suum <xliff:g id="FIRST">%1$d</xliff:g> protsenti"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Mine lehele <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"lk <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Valige tekst, millele kommentaar lisada"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Puudutage ala, millele kommentaar lisada"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Tühista"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF-i ei saa kuvada (<xliff:g id="TITLE">%1$s</xliff:g> on vales vormingus)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Lehekülge <xliff:g id="PAGE">%1$d</xliff:g> ei saa kuvada (viga failis)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Selle üksuse jaoks ei saa märkuste lisamise režiimi laadida."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: veebileht aadressil <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-posti aadress: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"lk <xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>-st"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Pilt: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Otsige failist"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Vasteid ei leitud."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Eelmine"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Järgmine"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Sule"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Faili muutmine"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Avamiseks sisestage parool"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Faili avamine nurjus. Probleem võib olla seotud lubadega."</string>
<string name="page_broken" msgid="2968770793669433462">"Rikutud leht PDF-dokumendis"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF-dokumendi töötlemiseks pole piisavalt andmeid"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-eu/strings.xml b/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
index 458acf0..20770aa 100644
--- a/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Fitxategi mota"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Fitxategi hau babestuta dago"</string>
<string name="label_password_first" msgid="4456258714097111908">"Pasahitza"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Pasahitza okerra da"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zooma ehuneko <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Joan <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>. orrira"</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g>garren orria"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Iruzkina egiteko, hautatu testua"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Sakatu iruzkindu beharreko eremua"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Utzi"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Ezin da erakutsi PDFa (<xliff:g id="TITLE">%1$s</xliff:g> fitxategiaren formatuak ez du balio)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Ezin da erakutsi <xliff:g id="PAGE">%1$d</xliff:g> orria (fitxategi-errorea)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Ezin da kargatu oharpenen modua elementu honetarako."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Esteka: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> domeinuko web-orria"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Esteka: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Helbide elektronikoa: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="FIRST">%1$d</xliff:g> eta <xliff:g id="LAST">%2$d</xliff:g> bitarteko orriak, guztira <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Irudia: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Bilatu fitxategia"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Ez da aurkitu emaitzarik."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Aurrekoa"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Hurrengoa"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Itxi"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Editatu fitxategia"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Idatzi pasahitza desblokeatzeko"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Ezin izan da ireki fitxategia. Agian ez duzu horretarako baimenik?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF dokumentuaren orria hondatuta dago"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Ez dago behar adina daturik PDF dokumentua prozesatzeko"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fa/strings.xml b/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
index 54bf7e7..0669b17 100644
--- a/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"نوع فایل"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"این فایل محافظت شده است"</string>
<string name="label_password_first" msgid="4456258714097111908">"گذرواژه"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"گذرواژه نادرست است"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"بزرگنمایی <xliff:g id="FIRST">%1$d</xliff:g> درصد"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"رفتن به صفحه <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"صفحه <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"نوشتار را برای نظر گذاشتن انتخاب کنید"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"روی قسمتی که میخواهید نظر بگذارید تکضرب بزنید"</string>
- <string name="action_cancel" msgid="5494417739210197522">"لغو کردن"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF نمایش داده نمیشود (قالب <xliff:g id="TITLE">%1$s</xliff:g> نامعتبر است)"</string>
<string name="error_on_page" msgid="1592475819957182385">"صفحه <xliff:g id="PAGE">%1$d</xliff:g> نمایش داده نمیشود (خطای فایل)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"حالت گزارمان برای این مورد بار نمیشود."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"پیوند: صفحه وب در <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"پیوند: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ایمیل: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"صفحههای <xliff:g id="FIRST">%1$d</xliff:g> تا <xliff:g id="LAST">%2$d</xliff:g> از <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"تصویر: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"پیدا کردن در فایل"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"مورد منطبقی یافت نشد."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"قبلی"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"بعدی"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"بستن"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ویرایش فایل"</string>
<string name="password_not_entered" msgid="8875370870743585303">"گذرواژه را برای بازگشایی قفل وارد کنید"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"فایل باز نشد. احتمالاً مشکلی در اجازه وجود دارد؟"</string>
<string name="page_broken" msgid="2968770793669433462">"صفحه سند PDF خراب است"</string>
<string name="needs_more_data" msgid="3520133467908240802">"دادهها برای پردازش سند PDF کافی نیست"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fi/strings.xml b/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
index 0b03cc8e..3518c19 100644
--- a/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Tiedostotyyppi"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Tämä tiedosto on suojattu"</string>
<string name="label_password_first" msgid="4456258714097111908">"Salasana"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Väärä salasana"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoomaus <xliff:g id="FIRST">%1$d</xliff:g> prosenttia"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Siirry sivulle <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"sivu <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Valitse teksti, johon haluat lisätä kommentin"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Napauta kommentoitavaa aluetta"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Peruuta"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF:ää ei voi näyttää (<xliff:g id="TITLE">%1$s</xliff:g> on virheellinen muoto)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Sivua <xliff:g id="PAGE">%1$d</xliff:g> ei voi näyttää (tiedostovirhe)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Kohteen muistiinpanotilaa ei voi ladata."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Linkki: verkkosivu osoitteessa <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Linkki: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Sähköpostiosoite: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"sivut <xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Kuva: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Etsi tiedostosta"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Osumia ei löytynyt."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Edellinen"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Seuraava"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Sulje"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Muokkaa tiedostoa"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Poista lukitus lisäämällä salasana"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Tiedoston avaaminen epäonnistui. Mahdollinen lupaan liittyvä ongelma?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF-dokumenttiin liittyvä sivu on rikki"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Riittämätön data PDF-dokumentin käsittelyyn"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml b/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
index ffa53ba..8a25992 100644
--- a/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Type de fichier"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Ce fichier est protégé"</string>
<string name="label_password_first" msgid="4456258714097111908">"Mot de passe"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Mot de passe incorrect"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> pour cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Accéder à la page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"page <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Sélect. du texte pour placer le commentaire"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Touchez une zone à commenter"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Annuler"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Impossible d\'afficher le PDF (format de <xliff:g id="TITLE">%1$s</xliff:g> non valide)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Impossible d\'afficher la page <xliff:g id="PAGE">%1$d</xliff:g> (erreur de fichier)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Impossible de charger le mode d\'annotation pour cet élément."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Lien : page Web sur <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Lien : <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Adresse de courriel : <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> à <xliff:g id="LAST">%2$d</xliff:g> sur <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image : <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Trouver dans fichier"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Aucune correspondance."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Précédent"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Suivant"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Fermer"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Modifier le fichier"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Entrez le mot de passe pour déverrouiller le fichier"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Échec de l\'ouverture du fichier. Problème d\'autorisation éventuel?"</string>
<string name="page_broken" msgid="2968770793669433462">"Page brisée pour le document PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Données insuffisantes pour le traitement du document PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fr/strings.xml b/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
index fd1ee39..dd5fcad 100644
--- a/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Type de fichier"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Ce fichier est protégé"</string>
<string name="label_password_first" msgid="4456258714097111908">"Mot de passe"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Mot de passe incorrect"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom : <xliff:g id="FIRST">%1$d</xliff:g> pour cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Accéder à la page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"page <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Sélectionnez le texte à commenter"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Appuyez sur la zone à commenter"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Annuler"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Impossible d\'afficher le PDF (format non valide pour <xliff:g id="TITLE">%1$s</xliff:g>)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Impossible d\'afficher la page <xliff:g id="PAGE">%1$d</xliff:g> (erreur de fichier)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Impossible de charger le mode Annotation pour cet élément."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Lien : page Web du domaine <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Lien : <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Adresse e-mail : <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> à <xliff:g id="LAST">%2$d</xliff:g> sur <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image : <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Rechercher dans fichier"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Aucune correspondance."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Précédent"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Suivant"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Fermer"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Modifier le fichier"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Saisissez le mot de passe pour procéder au déverrouillage"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Échec de l\'ouverture du fichier. Problème d\'autorisation possible ?"</string>
<string name="page_broken" msgid="2968770793669433462">"Page non fonctionnelle pour le document PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Données insuffisantes pour le traitement du document PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-gl/strings.xml b/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
index 5965cb5..ebe0816 100644
--- a/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Tipo de ficheiro"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Este ficheiro está protexido"</string>
<string name="label_password_first" msgid="4456258714097111908">"Contrasinal"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Contrasinal incorrecto"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom ao <xliff:g id="FIRST">%1$d</xliff:g> por cento"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Vai á páxina <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"páxina <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Selecciona o texto para engadir un comentario"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Toca a zona na que engadir un comentario"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancelar"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Non se puido mostrar o PDF (<xliff:g id="TITLE">%1$s</xliff:g> ten un formato non válido)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Non se puido mostrar a páxina <xliff:g id="PAGE">%1$d</xliff:g> (hai un erro no ficheiro)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Non se puido cargar o modo de anotación para este elemento."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Ligazón: páxina web en <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Ligazón: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Correo electrónico: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"páxinas da <xliff:g id="FIRST">%1$d</xliff:g> á <xliff:g id="LAST">%2$d</xliff:g> dun total de <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imaxe: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Busca no ficheiro"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Non se atopou ningunha coincidencia."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Anterior"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Seguinte"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Pechar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar o ficheiro"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Introduce o contrasinal para desbloquear o ficheiro"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Produciuse un erro ao abrir o ficheiro. É posible que haxa problemas co permiso?"</string>
<string name="page_broken" msgid="2968770793669433462">"Non funciona a páxina para o documento PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Os datos non son suficientes para procesar o documento PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-gu/strings.xml b/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
index e8bb6cf..ae95929 100644
--- a/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ફાઇલનો પ્રકાર"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"આ ફાઇલ સંરક્ષિત છે"</string>
<string name="label_password_first" msgid="4456258714097111908">"પાસવર્ડ"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"ખોટો પાસવર્ડ"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> ટકા નાનું-મોટું કરો"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"પેજ <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> પર જાઓ"</string>
<string name="desc_page" msgid="5684226167093594168">"પેજ <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"તમારી કૉમેન્ટ શામેલ કરવા ટેક્સ્ટ પસંદ કરો"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"કૉમેન્ટ કરવા માટે, કોઈ ભાગ પર ટૅપ કરો"</string>
- <string name="action_cancel" msgid="5494417739210197522">"રદ કરો"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF બતાવી શકાતી નથી (<xliff:g id="TITLE">%1$s</xliff:g>નું ફૉર્મેટ અમાન્ય છે)"</string>
<string name="error_on_page" msgid="1592475819957182385">"પેજ <xliff:g id="PAGE">%1$d</xliff:g> બતાવી શકાતું નથી (ફાઇલમાં ભૂલ)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"આ આઇટમ માટે ટીકાટિપ્પણીનો મોડ લોડ કરી શકાતો નથી."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"લિંક: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> પર વેબપેજ"</string>
<string name="desc_web_link" msgid="2776023299237058419">"લિંક: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ઇમેઇલ: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g>માંથી પેજ <xliff:g id="FIRST">%1$d</xliff:g>થી <xliff:g id="LAST">%2$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"છબી: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ફાઇલમાં શોધો"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"કોઈ મેળ મળ્યો નથી."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"પાછળ"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"આગળ"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"બંધ કરો"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ફાઇલમાં ફેરફાર કરો"</string>
<string name="password_not_entered" msgid="8875370870743585303">"અનલૉક કરવા માટે પાસવર્ડ દાખલ કરો"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ફાઇલ ખોલવામાં નિષ્ફળ રહ્યાં. શું તમારી પાસે આની પરવાનગી નથી?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF દસ્તાવેજ માટે પેજ લોડ થઈ રહ્યું નથી"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF દસ્તાવેજ પર પ્રક્રિયા કરવા માટે પર્યાપ્ત ડેટા નથી"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hi/strings.xml b/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
index 5d44d0d..49f8c1d 100644
--- a/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"फ़ाइल टाइप"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"यह फ़ाइल सुरक्षित है"</string>
<string name="label_password_first" msgid="4456258714097111908">"पासवर्ड"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"पासवर्ड गलत है"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> प्रतिशत ज़ूम करें"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> पेज पर जाएं"</string>
<string name="desc_page" msgid="5684226167093594168">"पेज <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"टिप्पणी करने के लिए टेक्स्ट चुनें"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"टिप्पणी करने के लिए किसी जगह पर टैप करें"</string>
- <string name="action_cancel" msgid="5494417739210197522">"रद्द करें"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF फ़ाइल नहीं दिखाई जा सकती (<xliff:g id="TITLE">%1$s</xliff:g> अमान्य फ़ॉर्मैट में है)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g> पेज नहीं दिखाया जा सकता (फ़ाइल में गड़बड़ी है)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"इस आइटम के लिए, एनोटेशन मोड लोड नहीं किया जा सका."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"लिंक: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> पर वेबपेज"</string>
<string name="desc_web_link" msgid="2776023299237058419">"लिंक: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ईमेल: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g> में से <xliff:g id="FIRST">%1$d</xliff:g> से <xliff:g id="LAST">%2$d</xliff:g> पेज"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"इमेज: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"फ़ाइल में खोजें"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"इससे मिलता-जुलता कोई नतीजा नहीं मिला."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"पीछे जाएं"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"आगे बढ़ें"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"बंद करें"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"फ़ाइल में बदलाव करें"</string>
<string name="password_not_entered" msgid="8875370870743585303">"अनलॉक करने के लिए पासवर्ड डालें"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"फ़ाइल नहीं खोली जा सकी. क्या आपके पास इसकी अनुमति नहीं है?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF दस्तावेज़ के लिए पेज लोड नहीं हो रहा है"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF दस्तावेज़ को प्रोसेस करने के लिए, ज़रूरत के मुताबिक डेटा नहीं है"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF फ़ाइल नहीं खोली जा सकी"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hr/strings.xml b/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
index a0c4be4..7248c6ee 100644
--- a/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Vrsta datoteke"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Datoteka je zaštićena"</string>
<string name="label_password_first" msgid="4456258714097111908">"Zaporka"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Zaporka nije točna"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zumiranje <xliff:g id="FIRST">%1$d</xliff:g> posto"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Idite na stranicu <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"stranica <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Odaberite tekst za postavljanje komentara"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Dodirnite područje koje ćete komentirati"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Odustani"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF se ne može prikazati (format <xliff:g id="TITLE">%1$s</xliff:g> nije važeći)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Stranica <xliff:g id="PAGE">%1$d</xliff:g> ne može se prikazati (pogreška datoteke)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Način za napomene za tu stavku ne može se učitati."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Veza: web-stranica na domeni <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Veza: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-adresa: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"stranice od <xliff:g id="FIRST">%1$d</xliff:g> do <xliff:g id="LAST">%2$d</xliff:g> od ukupno <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Slika: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Pronađi u datoteci"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nisu pronađena podudaranja."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Prethodno"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Sljedeće"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Zatvori"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Uređivanje datoteke"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Unesite zaporku za otključavanje"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Otvaranje datoteke nije uspjelo. Možda postoji problem s dopuštenjem?"</string>
<string name="page_broken" msgid="2968770793669433462">"Stranica je raščlanjena za PDF dokument"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nema dovoljno podataka za obradu PDF dokumenta"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hu/strings.xml b/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
index 41020ca..f63a921 100644
--- a/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Fájltípus"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"A fájl védett"</string>
<string name="label_password_first" msgid="4456258714097111908">"Jelszó"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Helytelen jelszó"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> százalékos nagyítás"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ugrás erre az oldalra: <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g>. oldal"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Válassza ki a szöveget a megjegyzéshez"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Koppintson arra a területre, amelyhez megjegyzést szeretne fűzni"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Mégse"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Nem sikerült megjeleníteni a PDF-et (<xliff:g id="TITLE">%1$s</xliff:g> formátuma érvénytelen)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Nem sikerült megjeleníteni a(z) <xliff:g id="PAGE">%1$d</xliff:g> oldalt (fájlhiba)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Az elemhez nem lehet betölteni kommentár módot."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: weboldal helye: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-mail-cím: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g>/<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>. oldal"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Kép: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Keresés a fájlban"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nincs találat."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Előző"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Következő"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Bezárás"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="TOTAL">%2$d</xliff:g>/<xliff:g id="POSITION">%1$d</xliff:g>."</string>
<string name="action_edit" msgid="5882082700509010966">"Fájl szerkesztése"</string>
<string name="password_not_entered" msgid="8875370870743585303">"A feloldáshoz írja be a jelszót"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Nem sikerült megnyitni a fájlt. Engedéllyel kapcsolatos problémáról lehet szó?"</string>
<string name="page_broken" msgid="2968770793669433462">"Az oldal nem tölt be a PDF-dokumentumban"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nem áll rendelkezésre elegendő adat a PDF-dokumentum feldolgozásához"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hy/strings.xml b/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
index 2e482a1..71d24c2 100644
--- a/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Ֆայլի տեսակը"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Այս ֆայլը պաշտպանված է"</string>
<string name="label_password_first" msgid="4456258714097111908">"Գաղտնաբառ"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Գաղտնաբառը սխալ է"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"մասշտաբ՝ <xliff:g id="FIRST">%1$d</xliff:g> տոկոս"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Անցեք <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> էջ"</string>
<string name="desc_page" msgid="5684226167093594168">"էջ <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Նշեք տեքստը՝ մեկնաբանություն տեղադրելու համար"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Հպեք՝ մեկնաբանություն ավելացնելու համար"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Չեղարկել"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Հնարավոր չէ ցուցադրել PDF-ը (<xliff:g id="TITLE">%1$s</xliff:g> ֆայլը անվավեր ձևաչափ ունի)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Հնարավոր չէ ցուցադրել էջ <xliff:g id="PAGE">%1$d</xliff:g>-ը (ֆայլի սխալ)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Չհաջողվեց բեռնել ծանոթագրության ռեժիմն այս տարրի համար։"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Հղում՝ կայքէջ <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>-ում"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Հղում՝ <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Էլ․ հասցե՝ <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"էջ <xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>՝ <xliff:g id="TOTAL">%3$d</xliff:g>-ից"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Պատկեր՝ <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Գտեք ֆայլում"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Համընկնումներ չեն հայտնաբերվել։"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Նախորդը"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Հաջորդը"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Փակել"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Փոփոխել ֆայլը"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Մուտքագրեք գաղտնաբառը՝ ապակողպելու համար"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Չհաջողվեց բացել ֆայլը։ Գուցե թույլտվության հետ կապված խնդի՞ր է։"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF փաստաթղթի էջը վնասված է"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Ոչ բավարար տվյալներ PDF փաստաթղթի մշակման համար"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-in/strings.xml b/pdf/pdf-viewer/src/main/res/values-in/strings.xml
index 0e9397c..3c9e624 100644
--- a/pdf/pdf-viewer/src/main/res/values-in/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-in/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Jenis file"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"File ini dilindungi"</string>
<string name="label_password_first" msgid="4456258714097111908">"Sandi"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Sandi salah"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> persen"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Buka halaman <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"halaman <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Pilih teks untuk memberikan komentar"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Ketuk area yang ingin dikomentari"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Batal"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Tidak dapat menampilkan PDF (format <xliff:g id="TITLE">%1$s</xliff:g> tidak valid)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Tidak dapat menampilkan halaman <xliff:g id="PAGE">%1$d</xliff:g> (kesalahan file)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Tidak dapat memuat mode anotasi untuk item ini."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: halaman web di <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"halaman <xliff:g id="FIRST">%1$d</xliff:g> sampai <xliff:g id="LAST">%2$d</xliff:g> dari <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Gambar: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Cari dalam file"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Tidak ada yang cocok."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Sebelumnya"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Berikutnya"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Tutup"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Masukkan sandi untuk membuka kunci"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Gagal membuka file. Kemungkinan masalah izin?"</string>
<string name="page_broken" msgid="2968770793669433462">"Halaman dokumen PDF rusak"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Data tidak cukup untuk memproses dokumen PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-is/strings.xml b/pdf/pdf-viewer/src/main/res/values-is/strings.xml
index 1a2f9ff..9378e05 100644
--- a/pdf/pdf-viewer/src/main/res/values-is/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-is/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Skráargerð"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Þessi skrá er varin"</string>
<string name="label_password_first" msgid="4456258714097111908">"Aðgangsorð"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Rangt aðgangsorð"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"stækka/minnka <xliff:g id="FIRST">%1$d</xliff:g> prósent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Fara á síðu <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"síða <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Veldu texta til að setja inn athugasemd"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Ýttu á svæði til að færa inn athugasemd"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Hætta við"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Ekki hægt að birta PDF (<xliff:g id="TITLE">%1$s</xliff:g> er á ógildu sniði)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Ekki hægt að birta síðuna <xliff:g id="PAGE">%1$d</xliff:g> (villa í skrá)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Ekki er hægt að hlaða textaskýringastillingu fyrir þetta atriði."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Tengill: vefsíða á <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Tengill: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Netfang: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"síður <xliff:g id="FIRST">%1$d</xliff:g> til <xliff:g id="LAST">%2$d</xliff:g> af <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Mynd: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Leita í skrá"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Ekkert fannst."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Fyrri"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Næsta"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Loka"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Breyta skrá"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Sláðu inn aðgangsorð til að opna"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Ekki tókst að opna skrána. Hugsanlega vandamál tengt heimildum?"</string>
<string name="page_broken" msgid="2968770793669433462">"Síða í PDF-skjali er gölluð"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Ekki næg gögn fyrir úrvinnslu á PDF-skjali"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-it/strings.xml b/pdf/pdf-viewer/src/main/res/values-it/strings.xml
index 3025646..89a8d17 100644
--- a/pdf/pdf-viewer/src/main/res/values-it/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-it/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Tipo di file"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Questo file è protetto"</string>
<string name="label_password_first" msgid="4456258714097111908">"Password"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Password errata"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g>%%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Vai alla pagina <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"pagina <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Seleziona il testo da inserire nel commento"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Tocca un\'area per inserire un commento"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Annulla"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Impossibile visualizzare PDF (<xliff:g id="TITLE">%1$s</xliff:g> è in un formato non valido)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Impossibile visualizzare la pagina <xliff:g id="PAGE">%1$d</xliff:g> (errore del file)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Impossibile caricare la modalità di annotazione per questo elemento."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: pagina web all\'indirizzo <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"pagine da <xliff:g id="FIRST">%1$d</xliff:g> a <xliff:g id="LAST">%2$d</xliff:g> di <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Immagine: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Trova nel file"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nessuna corrispondenza."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Indietro"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Avanti"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Chiudi"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Modifica file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Inserisci la password per sbloccare il file"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Impossibile aprire il file. Possibile problema di autorizzazione?"</string>
<string name="page_broken" msgid="2968770793669433462">"Pagina inaccessibile per il documento PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Dati insufficienti per l\'elaborazione del documento PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-iw/strings.xml b/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
index 0d21739..3dfd510 100644
--- a/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"סוג הקובץ"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"הקובץ הזה מוגן"</string>
<string name="label_password_first" msgid="4456258714097111908">"סיסמה"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"הסיסמה שגויה"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"שינוי מרחק התצוגה ב-<xliff:g id="FIRST">%1$d</xliff:g> אחוזים"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"מעבר לדף <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"עמוד <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"צריך לבחור את הטקסט לתגובה"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"אפשר להקיש על אזור כדי להגיב עליו"</string>
- <string name="action_cancel" msgid="5494417739210197522">"ביטול"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"אי אפשר להציג קובץ PDF (<xliff:g id="TITLE">%1$s</xliff:g> בפורמט לא תקין)"</string>
<string name="error_on_page" msgid="1592475819957182385">"לא ניתן להציג את הדף <xliff:g id="PAGE">%1$d</xliff:g> (שגיאת קובץ)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"אי אפשר לטעון את מצב ההערות בפריט הזה."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"קישור: דף אינטרנט בדומיין <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"קישור: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"אימייל: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"דפים <xliff:g id="FIRST">%1$d</xliff:g> עד <xliff:g id="LAST">%2$d</xliff:g> מתוך <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"תמונה: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"חיפוש בקובץ"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"לא נמצאו התאמות."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"הקודם"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"הבא"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"סגירה"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> מתוך <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"עריכת הקובץ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"צריך להזין סיסמה לביטול הנעילה"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"לא ניתן לפתוח את הקובץ. יכול להיות שיש בעיה בהרשאה."</string>
<string name="page_broken" msgid="2968770793669433462">"קישור מנותק בדף למסמך ה-PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"אין מספיק נתונים כדי לעבד את מסמך ה-PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ja/strings.xml b/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
index c5db90d..a48cc46 100644
--- a/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ファイル形式"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"このファイルは保護されています"</string>
<string name="label_password_first" msgid="4456258714097111908">"パスワード"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"パスワードが正しくありません"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"ズーム <xliff:g id="FIRST">%1$d</xliff:g> %%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> ページに移動します"</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g> ページ"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"コメントを配置するテキストを選択してください"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"コメント対象の範囲をタップしてください"</string>
- <string name="action_cancel" msgid="5494417739210197522">"キャンセル"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF を表示できません(<xliff:g id="TITLE">%1$s</xliff:g> の形式が無効です)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g> ページを表示できません(ファイルエラー)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"このアイテムのアノテーション モードを読み込めません。"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"リンク: ウェブページ(<xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>)"</string>
<string name="desc_web_link" msgid="2776023299237058419">"リンク: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"メールアドレス: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="FIRST">%1$d</xliff:g>~<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g> ページ"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"画像: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ファイル内を検索"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"一致する項目は見つかりませんでした。"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"前へ"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"次へ"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"閉じる"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ファイルを編集"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ロックを解除するには、パスワードを入力してください"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"ファイルを開けませんでした。権限に問題がある可能性はありませんか?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF ドキュメントのページが壊れています"</string>
<string name="needs_more_data" msgid="3520133467908240802">"データ不足のため PDF ドキュメントを処理できません"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ファイルを開けません"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ka/strings.xml b/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
index cc30d31..3fd5c9b 100644
--- a/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ფაილის ტიპი"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ეს ფაილი დაცულია"</string>
<string name="label_password_first" msgid="4456258714097111908">"პაროლი"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"პაროლი არასწორია"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"მასშტაბი <xliff:g id="FIRST">%1$d</xliff:g> პროცენტი"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g>-ე გვერდზე გადასვლა"</string>
<string name="desc_page" msgid="5684226167093594168">"გვერდი <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"კომენტარის განსათავსებლად მონიშნეთ ტექსტი"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"შეეხეთ ველს კომენტარის დასატოვებლად"</string>
- <string name="action_cancel" msgid="5494417739210197522">"გაუქმება"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF-ის ჩვენება შეუძლებელია (<xliff:g id="TITLE">%1$s</xliff:g> არასწორ ფორმატშია)"</string>
<string name="error_on_page" msgid="1592475819957182385">"გვერდის ჩვენება შეუძლებელია <xliff:g id="PAGE">%1$d</xliff:g> (ფაილის შეცდომა)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"ამ ერთეულისთვის ანოტაციის რეჟიმის ჩატვირთვა არ არის შესაძლებელი."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"ბმული: ვებგვერდი <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"ბმული: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ელფოსტა: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> გვერდები <xliff:g id="TOTAL">%3$d</xliff:g>-დან"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"სურათი: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ფაილში ძებნა"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"შესატყვისი ვერ მოიძებნა."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"წინა"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"შემდეგი"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"დახურვა"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ფაილის რედაქტირება"</string>
<string name="password_not_entered" msgid="8875370870743585303">"პაროლის შეყვანა განბლოკვისთვის"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"ფაილის გახსნა ვერ მოხერხდა. შესაძლოა ნებართვის პრობლემა იყოს?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF დოკუმენტის გვერდი დაზიანებულია"</string>
<string name="needs_more_data" msgid="3520133467908240802">"მონაცემები არ არის საკმარისი PDF დოკუმენტის დასამუშავებლად"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ფაილის გახსნა ვერ ხერხდება"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-kk/strings.xml b/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
index 84b91ca..5846202 100644
--- a/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Файл түрі"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Бұл файл қорғалған"</string>
<string name="label_password_first" msgid="4456258714097111908">"Құпия сөз"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Құпия сөз қате"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> пайызға масштабтау"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g>-бетке өтіңіз."</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g>-бет"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Пікір қалдыру үшін мәтін таңдаңыз."</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Пікір қалдыру үшін аймақты түртіңіз."</string>
- <string name="action_cancel" msgid="5494417739210197522">"Бас тарту"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF файлын көрсету мүмкін емес (<xliff:g id="TITLE">%1$s</xliff:g> форматы жарамсыз)."</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g>-бетті көрсету мүмкін емес (файл қатесі)."</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Бұл элемент үшін aннотация режимін жүктеу мүмкін емес."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Сілтеме: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> веб-беті"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Сілтеме: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Электрондық мекенжай: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"Бет: <xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Сурет: <xliff:g id="ALT_TEXT">%1$s</xliff:g>."</string>
<string name="hint_find" msgid="5385388836603550565">"Файлдан табу"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Ешқандай сәйкестік табылмады."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Алдыңғы"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Келесі"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Жабу"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Файлды өңдеу"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Құлыпты ашу үшін құпия сөзді енгізіңіз."</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Файл ашылмады. Бәлкім, рұқсатқа қатысты бір мәселе бар?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF құжатының беті бұзылған."</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF құжатын өңдеу үшін деректер жеткіліксіз."</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-km/strings.xml b/pdf/pdf-viewer/src/main/res/values-km/strings.xml
index 2e9bf3b..931c43f 100644
--- a/pdf/pdf-viewer/src/main/res/values-km/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-km/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ប្រភេទឯកសារ"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ឯកសារនេះត្រូវបានការពារ"</string>
<string name="label_password_first" msgid="4456258714097111908">"ពាក្យសម្ងាត់"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"ពាក្យសម្ងាត់មិនត្រឹមត្រូវ"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"ពង្រីកបង្រួម <xliff:g id="FIRST">%1$d</xliff:g> ភាគរយ"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"ទៅទំព័រទី <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"ទំព័រទី <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"ជ្រើសរើសអក្សរ ដើម្បីផ្ដល់មតិរបស់អ្នក"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"ចុចកន្លែងណាមួយដើម្បីផ្ដល់មតិ"</string>
- <string name="action_cancel" msgid="5494417739210197522">"បោះបង់"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"មិនអាចបង្ហាញ PDF បានទេ (<xliff:g id="TITLE">%1$s</xliff:g> មានទម្រង់មិនត្រឹមត្រូវ)"</string>
<string name="error_on_page" msgid="1592475819957182385">"មិនអាចបង្ហាញទំព័រទី <xliff:g id="PAGE">%1$d</xliff:g> បានទេ (បញ្ហាឯកសារ)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"មិនអាចផ្ទុកមុខងារចំណារសម្រាប់ធាតុនេះបានទេ។"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"តំណ៖ ទំព័របណ្ដាញនៅ <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"តំណ៖ <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"អ៊ីមែល៖ <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"ទំព័រទី <xliff:g id="FIRST">%1$d</xliff:g> ដល់ <xliff:g id="LAST">%2$d</xliff:g> នៃ <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"រូបភាព៖ <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ស្វែងរកនៅក្នុងឯកសារ"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"រកមិនឃើញអ្វីដែលត្រូវគ្នាទេ។"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"មុន"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"បន្ទាប់"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"បិទ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"កែឯកសារ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"បញ្ចូលពាក្យសម្ងាត់ ដើម្បីដោះសោ"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"មិនអាចបើកឯកសារនេះបានទេ។ អាចមានបញ្ហានៃការអនុញ្ញាតឬ?"</string>
<string name="page_broken" msgid="2968770793669433462">"ទំព័រមិនដំណើរការសម្រាប់ឯកសារ PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"មានទិន្នន័យមិនគ្រប់គ្រាន់សម្រាប់ដំណើរការឯកសារ PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-kn/strings.xml b/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
index 00aa9bb..fb8b009 100644
--- a/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ಫೈಲ್ ಪ್ರಕಾರ"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ಈ ಫೈಲ್ ಅನ್ನು ಸಂರಕ್ಷಿಸಲಾಗಿದೆ"</string>
<string name="label_password_first" msgid="4456258714097111908">"ಪಾಸ್ವರ್ಡ್"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"ಪಾಸ್ವರ್ಡ್ ತಪ್ಪಾಗಿದೆ"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"ಶೇಕಡಾ <xliff:g id="FIRST">%1$d</xliff:g> ರಷ್ಟು ಝೂಮ್ ಮಾಡಿ"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"ಪುಟ <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> ಕ್ಕೆ ತೆರಳಿ"</string>
<string name="desc_page" msgid="5684226167093594168">"ಪುಟ <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"ನಿಮ್ಮ ಕಾಮೆಂಟ್ ಅನ್ನು ಇರಿಸಲು ಪಠ್ಯವನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"ಕಾಮೆಂಟ್ ಮಾಡಲು ಪ್ರದೇಶವನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
- <string name="action_cancel" msgid="5494417739210197522">"ರದ್ದುಮಾಡಿ"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF ಅನ್ನು ಪ್ರದರ್ಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ (<xliff:g id="TITLE">%1$s</xliff:g> ಅಮಾನ್ಯವಾದ ಫಾರ್ಮ್ಯಾಟ್ ಆಗಿದೆ)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g> ಪುಟವನ್ನು ಪ್ರದರ್ಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ (ಫೈಲ್ ದೋಷ)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"ಈ ಐಟಂಗೆ ಟಿಪ್ಪಣಿಯನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"ಲಿಂಕ್: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> ನಲ್ಲಿರುವ ವೆಬ್ಪುಟ"</string>
<string name="desc_web_link" msgid="2776023299237058419">"ಲಿಂಕ್: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ಇಮೇಲ್: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="FIRST">%1$d</xliff:g> ನಿಂದ <xliff:g id="LAST">%2$d</xliff:g> ವರೆಗಿನ <xliff:g id="TOTAL">%3$d</xliff:g> ಪುಟಗಳು"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"ಚಿತ್ರ: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ಫೈಲ್ನಲ್ಲಿ ಹುಡುಕಿ"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"ಯಾವುದೇ ಹೊಂದಾಣಿಕೆಗಳು ಕಂಡುಬಂದಿಲ್ಲ."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"ಹಿಂದಿನದು"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"ಮುಂದಿನದು"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"ಮುಚ್ಚಿರಿ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ಫೈಲ್ ಎಡಿಟ್ ಮಾಡಿ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ಅನ್ಲಾಕ್ ಮಾಡಲು ಪಾಸವರ್ಡ್ ಅನ್ನು ನಮೂದಿಸಿ"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ಫೈಲ್ ತೆರೆಯಲು ವಿಫಲವಾಗಿದೆ. ಸಂಭವನೀಯ ಅನುಮತಿ ಸಮಸ್ಯೆ?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF ಡಾಕ್ಯುಮೆಂಟ್ಗೆ ಸಂಬಂಧಿಸಿದ ಪುಟ ಮುರಿದಿದೆ"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ಡಾಕ್ಯುಮೆಂಟ್ ಅನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಸಾಕಷ್ಟು ಡೇಟಾ ಇಲ್ಲ"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ko/strings.xml b/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
index 8189d99..9530691 100644
--- a/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"파일 형식"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"보호된 파일임"</string>
<string name="label_password_first" msgid="4456258714097111908">"비밀번호"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"비밀번호가 잘못됨"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g>%% 확대/축소"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> 페이지로 이동"</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g>페이지"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"댓글을 달려는 텍스트를 선택하세요."</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"댓글을 달려는 영역을 탭하세요."</string>
- <string name="action_cancel" msgid="5494417739210197522">"취소"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"<xliff:g id="TITLE">%1$s</xliff:g>의 형식이 잘못되어 PDF를 표시할 수 없음"</string>
<string name="error_on_page" msgid="1592475819957182385">"파일 오류로 <xliff:g id="PAGE">%1$d</xliff:g> 페이지를 표시할 수 없음"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"이 항목에 대한 주석 모드를 로드할 수 없습니다."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"링크: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>의 웹페이지"</string>
<string name="desc_web_link" msgid="2776023299237058419">"링크: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"이메일: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g>페이지 중 <xliff:g id="FIRST">%1$d</xliff:g>~<xliff:g id="LAST">%2$d</xliff:g>페이지"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"이미지: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"파일에서 찾기"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"일치하는 항목이 없습니다."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"이전"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"다음"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"닫기"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"파일 수정"</string>
<string name="password_not_entered" msgid="8875370870743585303">"잠금 해제하려면 비밀번호 입력"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"파일을 열 수 없습니다. 권한 문제가 있을 수 있나요?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF 문서의 페이지가 손상되었습니다."</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF 문서 처리를 위한 데이터가 부족합니다."</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ky/strings.xml b/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
index a0e0558..92e5119 100644
--- a/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Файл түрү"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Бул файл корголгон"</string>
<string name="label_password_first" msgid="4456258714097111908">"Сырсөз"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Сырсөз туура эмес"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> пайыз чоңойтуу/кичирейтүү"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> бетине өтүңүз"</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g>-бет"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Пикирди жайгаштыруу үчүн текст тандаңыз"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Пикир билдирүү үчүн тийиштүү жерди басыңыз"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Жокко чыгаруу"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF\'ти көрсөтүү мүмкүн эмес (<xliff:g id="TITLE">%1$s</xliff:g> форматы жараксыз)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g> бетти көрсөтүү мүмкүн эмес (файл катасы)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Бул нерсе үчүн аннотация түзүү режими жүктөлгөн жок."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Шилтеме: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> веб-баракчасы"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Шилтеме: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Электрондук почта: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g> ичинен <xliff:g id="FIRST">%1$d</xliff:g>—<xliff:g id="LAST">%2$d</xliff:g>-беттер"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Сүрөт: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Файлдан издөө"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Эч нерсе табылган жок."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Мурунку"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Кийинки"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Жабуу"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Файлды түзөтүү"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Кулпусун ачуу үчүн сырсөздү териңиз"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Файл ачылган жок. Керектүү уруксаттар жок окшойт."</string>
<string name="page_broken" msgid="2968770793669433462">"PDF документинин барагы бузук"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF документин иштетүү үчүн маалымат жетишсиз"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-lo/strings.xml b/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
index 06a320b..baa1c28 100644
--- a/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ປະເພດໄຟລ໌"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ໄຟລ໌ນີ້ຖືກປ້ອງກັນໄວ້"</string>
<string name="label_password_first" msgid="4456258714097111908">"ລະຫັດຜ່ານ"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"ຊູມ <xliff:g id="FIRST">%1$d</xliff:g> ເປີເຊັນ"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"ໄປທີ່ໜ້າ <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"ໜ້າ <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"ເລືອກຂໍ້ຄວາມເພື່ອວາງຄຳເຫັນຂອງທ່ານ"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"ແຕະໃສ່ພື້ນທີ່ໃດໜຶ່ງເພື່ອຂຽນຄຳເຫັນໃສ່"</string>
- <string name="action_cancel" msgid="5494417739210197522">"ຍົກເລີກ"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"ບໍ່ສາມາດສະແດງ PDF (<xliff:g id="TITLE">%1$s</xliff:g> ມີຮູບແບບທີ່ບໍ່ຖືກຕ້ອງ)"</string>
<string name="error_on_page" msgid="1592475819957182385">"ບໍ່ສາມາດສະແດງໜ້າ <xliff:g id="PAGE">%1$d</xliff:g> (ໄຟລ໌ຜິດພາດ)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"ບໍ່ສາມາດໂຫຼດໂໝດການອະທິບາຍຄວາມເຫັນສຳລັບລາຍການນີ້ໄດ້."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"ລິ້ງ: ໜ້າເວັບທີ່ <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"ລິ້ງ: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ອີເມວ: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"ໜ້າທີ <xliff:g id="FIRST">%1$d</xliff:g> ຫາ <xliff:g id="LAST">%2$d</xliff:g> ຈາກທັງໝົດ <xliff:g id="TOTAL">%3$d</xliff:g> ໜ້າ"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"ຮູບ: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ຊອກຫາໃນໄຟລ໌"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"ບໍ່ພົບຂໍ້ມູນທີ່ກົງກັນ."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"ກ່ອນໜ້າ"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"ຕໍ່ໄປ"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"ປິດ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ແກ້ໄຂໄຟລ໌"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ໃສ່ລະຫັດເພື່ອປົດລັອກ"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ເປີດໄຟລ໌ບໍ່ສຳເລັດ. ອາດເປັນຍ້ອນບັນຫາທາງການອະນຸຍາດບໍ?"</string>
<string name="page_broken" msgid="2968770793669433462">"ໜ້າເສຍຫາຍສໍາລັບເອກະສານ PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"ຂໍ້ມູນບໍ່ພຽງພໍສໍາລັບການປະມວນຜົນເອກະສານ PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-lt/strings.xml b/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
index 2d6a562..588ff87 100644
--- a/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Failo tipas"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Šis failas yra apsaugotas"</string>
<string name="label_password_first" msgid="4456258714097111908">"Slaptažodis"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Slaptažodis netinkamas"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"keisti mastelį <xliff:g id="FIRST">%1$d</xliff:g> proc."</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Eiti į <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> puslapį"</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g> psl."</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Pasirinkite tekstą, kad pateiktumėte komentarą"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Palieskite komentuotiną sritį"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Atšaukti"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Negalima pateikti PDF („<xliff:g id="TITLE">%1$s</xliff:g>“ netinkamo formato)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Negalima pateikti <xliff:g id="PAGE">%1$d</xliff:g> puslapio (failo klaida)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Nepavyko įkelti šio elemento komentaro režimo."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Nuoroda: tinklalapis adresu <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Nuoroda: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"El. pašto adresas: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> psl. iš <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Vaizdas: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Rasti failą"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nerasta atitikčių."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Ankstesnis"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Kitas"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Uždaryti"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Redaguoti failą"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Įveskite slaptažodį, kad atrakintumėte"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Nepavyko atidaryti failo. Galima su leidimais susijusi problema?"</string>
<string name="page_broken" msgid="2968770793669433462">"Sugadintas PDF dokumento puslapis"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nepakanka duomenų PDF dokumentui apdoroti"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-lv/strings.xml b/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
index 84afd6d..738615d 100644
--- a/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Faila tips"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Šis fails ir aizsargāts"</string>
<string name="label_password_first" msgid="4456258714097111908">"Parole"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Parole nav pareiza"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"tālummaiņa procentos ir <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Doties uz <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>. lapu"</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g>. lapa"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Atlasiet tekstu, lai pievienotu komentāru"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Pieskarieties komentējamajam apgabalam"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Atcelt"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Nevar parādīt PDF (faila “<xliff:g id="TITLE">%1$s</xliff:g>” formāts nav derīgs)."</string>
<string name="error_on_page" msgid="1592475819957182385">"Nevar parādīt <xliff:g id="PAGE">%1$d</xliff:g>. lapu (faila kļūda)."</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Šim vienumam nevar ielādēt anotēšanas režīmu."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Saite: tīmekļa lapa domēnā <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Saite: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-pasta adrese: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="FIRST">%1$d</xliff:g>.–<xliff:g id="LAST">%2$d</xliff:g>. lapa no <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Attēls: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Meklēt failā"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nav atrasta neviena atbilstība."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Atpakaļ"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Tālāk"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Aizvērt"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Rediģēt failu"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Lai atbloķētu, ievadiet paroli."</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Neizdevās atvērt failu. Iespējams, ir radusies problēma ar atļaujām."</string>
<string name="page_broken" msgid="2968770793669433462">"PDF dokumenta lapa ir bojāta"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nepietiekams datu apjoms, lai apstrādātu PDF dokumentu"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-mk/strings.xml b/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
index 854802a..e6b85c2 100644
--- a/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Вид датотека"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Датотекава е заштитена"</string>
<string name="label_password_first" msgid="4456258714097111908">"Лозинка"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Лозинката е неточна"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"зумирајте <xliff:g id="FIRST">%1$d</xliff:g> проценти"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Одете на страницата <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"страница <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Изберете текст за поставување на коментарот"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Допрете област за која ќе коментирате"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Откажи"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Не може да се прикаже PDF (<xliff:g id="TITLE">%1$s</xliff:g> е со неважечки формат)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Не може да се прикаже страницата <xliff:g id="PAGE">%1$d</xliff:g> (грешка во датотеката)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Не може да се вчита „Режимот за прибелешки“ за ставкава."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Линк: веб-страница на <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Линк: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Е-пошта: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"од страница <xliff:g id="FIRST">%1$d</xliff:g> до <xliff:g id="LAST">%2$d</xliff:g> од <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Слика: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Најдете во датотека"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Не се најдени совпаѓања."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Претходно"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Следно"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Затвори"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Изменете ја датотеката"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Внесете лозинка за да отклучите"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Не можеше да се отвори датотеката. Можеби има проблем со дозволата?"</string>
<string name="page_broken" msgid="2968770793669433462">"Страницата не може да го вчита PDF-документот"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Недоволно податоци за обработка на PDF-документот"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ml/strings.xml b/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
index 516f0cc..760a696 100644
--- a/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ഫയല് തരം"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ഈ ഫയൽ പരിരക്ഷിതമാണ്"</string>
<string name="label_password_first" msgid="4456258714097111908">"പാസ്വേഡ്"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"പാസ്വേഡ് തെറ്റാണ്"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> ശതമാനം സൂം ചെയ്യുക"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> പേജിലേക്ക് പോകുക"</string>
<string name="desc_page" msgid="5684226167093594168">"പേജ് <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"നിങ്ങളുടെ കമന്റ് നൽകാൻ ടെക്സ്റ്റ് തിരഞ്ഞെടുക്കുക"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"കമന്റിടേണ്ട ഏരിയ ടാപ്പ് ചെയ്യുക"</string>
- <string name="action_cancel" msgid="5494417739210197522">"റദ്ദാക്കുക"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF ദൃശ്യമാക്കാനാവില്ല (<xliff:g id="TITLE">%1$s</xliff:g>, അസാധുവായ ഫോർമാറ്റിലാണ്)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g> പേജ് പ്രദർശിപ്പിക്കാനാവില്ല (ഫയൽ പിശക്)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"ഈ ഇനത്തിന് അനോട്ടേഷൻ മോഡ് ലോഡ് ചെയ്യാനാകില്ല."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"ലിങ്ക്: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> എന്നതിലെ വെബ്പേജ്"</string>
<string name="desc_web_link" msgid="2776023299237058419">"ലിങ്ക്: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ഇമെയിൽ: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g>-ൽ <xliff:g id="FIRST">%1$d</xliff:g> മുതൽ <xliff:g id="LAST">%2$d</xliff:g> വരെയുള്ള പേജുകൾ"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"ചിത്രം: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ഫയലിൽ കണ്ടെത്തുക"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"പൊരുത്തങ്ങളൊന്നും കണ്ടെത്തിയില്ല."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"മുമ്പത്തേത്"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"അടുത്തത്"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"അടയ്ക്കുക"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ഫയൽ എഡിറ്റ് ചെയ്യുക"</string>
<string name="password_not_entered" msgid="8875370870743585303">"അൺലോക്ക് ചെയ്യാൻ പാസ്വേഡ് നൽകുക"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ഫയൽ തുറക്കാനായില്ല. അനുമതി സംബന്ധിച്ച പ്രശ്നമാകാൻ സാധ്യതയുണ്ടോ?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF ഡോക്യുമെന്റിനായി പേജ് ലോഡ് ചെയ്യാനായില്ല"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ഡോക്യുമെന്റ് പ്രോസസ് ചെയ്യാൻ മതിയായ ഡാറ്റയില്ല"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-mn/strings.xml b/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
index 65ed1dc..9c8f052 100644
--- a/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Файлын төрөл"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Энэ файл хамгаалагдсан"</string>
<string name="label_password_first" msgid="4456258714097111908">"Нууц үг"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Нууц үг буруу байна"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> хувь томруулсан"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g>-р хуудсанд очих"</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g>-р хуудас"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Сэтгэгдлээ байрлуулахын тулд текст сонгоно уу"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Сэтгэгдэл бичих хэсэг дээр товшино уу"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Цуцлах"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF-г үзүүлэх боломжгүй (<xliff:g id="TITLE">%1$s</xliff:g>-н формат буруу байна)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g> хуудсыг үзүүлэх боломжгүй (файлын алдаа)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Энэ зүйлд тэмдэглэгээний горимыг ачаалах боломжгүй."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Холбоос: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> дээрх веб хуудас"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Холбоос: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Имэйл: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"Нийт <xliff:g id="TOTAL">%3$d</xliff:g> хуудасны <xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>-р хуудаснууд"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Зураг: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Файлаас олох"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Ямар ч таарц олдсонгүй."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Өмнөх"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Дараах"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Хаах"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Файлыг засах"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Түгжээг тайлахын тулд нууц үг оруулна уу"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Файлыг нээж чадсангүй. Зөвшөөрөлтэй холбоотой асуудал байж болох уу?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF баримт бичгийн хуудас эвдэрсэн"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF баримт бичгийг боловсруулахад өгөгдөл хангалтгүй байна"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-mr/strings.xml b/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
index ae238c2..40ee1a8 100644
--- a/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"फाइल प्रकार"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ही फाइल संरक्षित आहे"</string>
<string name="label_password_first" msgid="4456258714097111908">"पासवर्ड"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"पासवर्ड चुकीचा आहे"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> टक्के झूम करा"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"पेज <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> वर जा"</string>
<string name="desc_page" msgid="5684226167093594168">"पेज <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"तुमच्या टिप्पणी ठेवण्यासाठी मजकूर निवडा"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"टिप्पणी करायच्या क्षेत्रावर टॅप करा"</string>
- <string name="action_cancel" msgid="5494417739210197522">"रद्द करा"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF दाखवू शकत नाही (<xliff:g id="TITLE">%1$s</xliff:g> चा फॉरमॅट चुकीचा आहे)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g> पेज दाखवू शकत नाही (फाइल एरर)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"या आयटमसाठी भाष्य मोड लोड करू शकत नाही."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"लिंक: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> वरील वेबपेज"</string>
<string name="desc_web_link" msgid="2776023299237058419">"लिंक: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ईमेल: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g> पैकी पेज <xliff:g id="FIRST">%1$d</xliff:g> ते <xliff:g id="LAST">%2$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"इमेज: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"फाइल शोधा"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"कोणत्याही जुळण्या आढळल्या नाहीत."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"मागील"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"पुढील"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"बंद करा"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"फाइल संपादित करा"</string>
<string name="password_not_entered" msgid="8875370870743585303">"अनलॉक करण्यासाठी पासवर्ड एंटर करा"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"फाइल उघडता आली नाही. परवानगीशी संबंधित संभाव्य समस्या?"</string>
<string name="page_broken" msgid="2968770793669433462">"पीडीएफ दस्तऐवजासाठी पेज खंडित झाले आहे"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF दस्तऐवजावर प्रक्रिया करण्यासाठी डेटा पुरेसा नाही"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ms/strings.xml b/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
index b42f674..f905244 100644
--- a/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Jenis fail"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Fail ini dilindungi"</string>
<string name="label_password_first" msgid="4456258714097111908">"Kata laluan"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Kata laluan salah"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zum <xliff:g id="FIRST">%1$d</xliff:g> peratus"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Akses halaman <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"halaman <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Pilih teks untuk meletakkan ulasan anda"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Ketik bahagian untuk diulas"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Batal"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Tidak dapat memaparkan PDF (format untuk <xliff:g id="TITLE">%1$s</xliff:g> tidak sah)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Tidak dapat memaparkan halaman <xliff:g id="PAGE">%1$d</xliff:g> (ralat fail)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Tidak dapat memuatkan mod anotasi untuk item ini."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Pautan: halaman web pada <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Pautan: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-mel: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"halaman <xliff:g id="FIRST">%1$d</xliff:g> hingga <xliff:g id="LAST">%2$d</xliff:g> daripada <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imej: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Temukan dalam fail"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Tiada padanan ditemukan."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Sebelumnya"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Seterusnya"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Tutup"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit fail"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Masukkan kata laluan untuk membuka kunci"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Gagal membuka fail. Kemungkinan terdapat masalah berkaitan dengan kebenaran?"</string>
<string name="page_broken" msgid="2968770793669433462">"Halaman rosak untuk dokumen PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Data tidak mencukupi untuk memproses dokumen PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-my/strings.xml b/pdf/pdf-viewer/src/main/res/values-my/strings.xml
index ecaf6e9..c288df3 100644
--- a/pdf/pdf-viewer/src/main/res/values-my/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-my/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ဖိုင်အမျိုးအစား"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ဤဖိုင်ကို ကာကွယ်ထားသည်"</string>
<string name="label_password_first" msgid="4456258714097111908">"စကားဝှက်"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"စကားဝှက် မှားနေသည်"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"ဇူးမ် <xliff:g id="FIRST">%1$d</xliff:g> ရာခိုင်နှုန်း"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"စာမျက်နှာ <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> သို့"</string>
<string name="desc_page" msgid="5684226167093594168">"စာမျက်နှာ <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"သင်၏မှတ်ချက်ထည့်ရန် စာသားကို ရွေးပါ"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"မှတ်ချက်ပေးရန် နေရာကို တို့ပါ"</string>
- <string name="action_cancel" msgid="5494417739210197522">"မလုပ်တော့"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF ကို ပြ၍မရပါ (<xliff:g id="TITLE">%1$s</xliff:g> သည် မမှန်ကန်သော ဖော်မက်ဖြစ်သည်)"</string>
<string name="error_on_page" msgid="1592475819957182385">"စာမျက်နှာ <xliff:g id="PAGE">%1$d</xliff:g> ကို ပြ၍မရပါ (ဖိုင်အမှား)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"ဤအကြောင်းအရာအတွက် မှတ်ချက်မုဒ် ဖွင့်၍မရပါ။"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"လင့်ခ်- <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> ရှိ ဝဘ်စာမျက်နှာ"</string>
<string name="desc_web_link" msgid="2776023299237058419">"လင့်ခ်- <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"အီးမေးလ်- <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"စာမျက်နှာ <xliff:g id="TOTAL">%3$d</xliff:g> အနက် <xliff:g id="FIRST">%1$d</xliff:g> မှ <xliff:g id="LAST">%2$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"ပုံ- <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ဖိုင်တွင် ရှာရန်"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"ကိုက်ညီမှု မတွေ့ပါ။"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"ယခင်"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"ရှေ့သို့"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"ပိတ်ရန်"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ဖိုင် တည်းဖြတ်ရန်"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ဖွင့်ရန် စကားဝှက်ထည့်ပါ"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ဖိုင်ကို ဖွင့်၍မရလိုက်ပါ။ ခွင့်ပြုချက် ပြဿနာ ဖြစ်နိုင်လား။"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF မှတ်တမ်းအတွက် စာမျက်နှာ ပျက်နေသည်"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF မှတ်တမ်း လုပ်ဆောင်ရန်အတွက် ဒေတာ မလုံလောက်ပါ"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-nb/strings.xml b/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
index 9bb458d..06e5547 100644
--- a/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Filtype"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Denne filen er beskyttet"</string>
<string name="label_password_first" msgid="4456258714097111908">"Passord"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Passordet er feil"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> prosent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Gå til side <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"side <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Merk tekst for å sette inn kommentaren din"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Trykk på et område du vil kommentere"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Avbryt"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Kan ikke vise PDF-filen (<xliff:g id="TITLE">%1$s</xliff:g> har ugyldig format)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Kan ikke vise side <xliff:g id="PAGE">%1$d</xliff:g> (filfeil)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Kan ikke laste inn annoteringsmodus for dette elementet."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: nettside på <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-postadresse: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"side <xliff:g id="FIRST">%1$d</xliff:g> til <xliff:g id="LAST">%2$d</xliff:g> av <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Bilde: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Finn i filen"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Fant ingen treff."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Forrige"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Neste"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Lukk"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Endre filen"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Skriv inn passordet for å låse opp"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Kunne ikke åpne filen. Kan det være et problem med tillatelser?"</string>
<string name="page_broken" msgid="2968770793669433462">"Siden er ødelagt for PDF-dokumentet"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Det er utilstrekkelige data for behandling av PDF-dokumentet"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ne/strings.xml b/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
index 346ef60..d847ed0c 100644
--- a/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"फाइलको प्रकार"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"यो फाइलमा पासवर्ड राखिएको छ"</string>
<string name="label_password_first" msgid="4456258714097111908">"पासवर्ड"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"पासवर्ड मिलेन"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> प्रतिशत जुम गर्नुहोस्"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"पेज <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> मा जानुहोस्"</string>
<string name="desc_page" msgid="5684226167093594168">"पेज <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"आफ्नो कमेन्ट गर्न टेक्स्ट चयन गर्नुहोस्"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"कमेन्ट गर्नु पर्ने क्षेत्रमा ट्याप गर्नुहोस्"</string>
- <string name="action_cancel" msgid="5494417739210197522">"रद्द गर्नुहोस्"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF देखाउन मिल्दैन (<xliff:g id="TITLE">%1$s</xliff:g> को फर्म्याट अवैध छ)"</string>
<string name="error_on_page" msgid="1592475819957182385">"पेज <xliff:g id="PAGE">%1$d</xliff:g> देखाउन मिल्दैन (फाइलसम्बन्धी त्रुटि)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"यो वस्तुमा एनोटेसन मोड लोड गर्न मिल्दैन।"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"लिंक: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> मा भएको वेबपेज"</string>
<string name="desc_web_link" msgid="2776023299237058419">"लिंक: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"इमेल: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g> मध्ये <xliff:g id="LAST">%2$d</xliff:g> देखि <xliff:g id="FIRST">%1$d</xliff:g> पेजहरू"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"फोटो: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"फाइलमा खोज्नुहोस्"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"मिल्दोजुल्दो परिणाम भेटिएन।"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"अघिल्लो"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"अर्को"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"बन्द गर्नुहोस्"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"फाइल सम्पादन गर्नुहोस्"</string>
<string name="password_not_entered" msgid="8875370870743585303">"अनलक गर्न पासवर्ड हाल्नुहोस्"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"फाइल खोल्न सकिएन। तपाईंसँग यो फाइल खोल्ने अनुमति छैन?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF डकुमेन्टको पेज लोड गर्न सकिएन"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF डकुमेन्ट प्रोसेस गर्न पर्याप्त जानकारी छैन"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-night-v34/colors.xml b/pdf/pdf-viewer/src/main/res/values-night-v34/colors.xml
deleted file mode 100644
index 2b2a21c..0000000
--- a/pdf/pdf-viewer/src/main/res/values-night-v34/colors.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<!--
- Copyright 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<resources>
-
- <color name="google_blue">#ff1a73e8</color>
- <color name="google_white">#ffffffff</color>
- <color name="google_grey">#ff3c4043</color>
- <color name="text_default">#666</color>
- <color name="text_error">#da4336</color>
- <color name="selection_handles">#00aadd</color>
- <color name="search_background">@android:color/system_surface_container_dark</color>
- <color name="search_textbox">@android:color/system_surface_bright_dark</color>
- <color name="search_texthint">@android:color/system_on_surface_variant_dark</color>
- <color name="search_textColor">@android:color/system_on_surface_dark</color>
- <color name="search_count">@android:color/system_on_surface_variant_dark</color>
- <color name="search_prev_button">@android:color/system_on_surface_variant_dark</color>
- <color name="search_next_button">@android:color/system_on_surface_variant_dark</color>
- <color name="search_close_button">@android:color/system_on_surface_variant_dark</color>
-
-
-</resources>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/values-night/colors.xml b/pdf/pdf-viewer/src/main/res/values-night/colors.xml
index 3e27f46..ea1e693 100644
--- a/pdf/pdf-viewer/src/main/res/values-night/colors.xml
+++ b/pdf/pdf-viewer/src/main/res/values-night/colors.xml
@@ -1,5 +1,5 @@
-<!--
- Copyright 2023 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2024 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.
@@ -15,21 +15,7 @@
-->
<resources>
-
- <color name="google_blue">#ff1a73e8</color>
- <color name="google_white">#ffffffff</color>
- <color name="google_grey">#ff3c4043</color>
- <color name="text_default">#666</color>
- <color name="text_error">#da4336</color>
- <color name="selection_handles">#00aadd</color>
- <color name="search_background">#241F17</color>
- <color name="search_textbox">#3E382F</color>
- <color name="search_texthint">#EBE1D4</color>
- <color name="search_textColor">#EBE1D4</color>
- <color name="search_count">#D1C5B4</color>
- <color name="search_prev_button">#D1C5B4</color>
- <color name="search_next_button">#D1C5B4</color>
- <color name="search_close_button">#D1C5B4</color>
-
-
-</resources>
\ No newline at end of file
+ <color name="pdf_viewer_color_primary">@color/m3_sys_color_dark_primary</color>
+ <color name="pdf_viewer_color_on_surface">@color/m3_sys_color_dark_on_surface</color>
+ <color name="pdf_viewer_color_error">@color/m3_sys_color_dark_error</color>
+</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-nl/strings.xml b/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
index a8a14e9..5ceea0c 100644
--- a/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Bestandstype"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Dit bestand is beveiligd"</string>
<string name="label_password_first" msgid="4456258714097111908">"Wachtwoord"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Wachtwoord is onjuist"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> procent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ga naar pagina <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"pagina <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Selecteer tekst om je reactie te plaatsen"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Tik op een gedeelte waarop je wilt reageren"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Annuleren"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Kan pdf niet weergeven (indeling van <xliff:g id="TITLE">%1$s</xliff:g> is ongeldig)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Kan pagina <xliff:g id="PAGE">%1$d</xliff:g> niet weergeven (bestandsfout)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Kan de annotatiemodus voor dit item niet laden."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: webpagina op <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-mail: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"pagina\'s <xliff:g id="FIRST">%1$d</xliff:g> tot en met <xliff:g id="LAST">%2$d</xliff:g> van <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Afbeelding: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Zoeken in bestand"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Geen overeenkomsten gevonden."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Vorige"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Volgende"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Sluiten"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Bestand bewerken"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Voer het wachtwoord in om te ontgrendelen"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Kan het bestand niet openen. Mogelijk rechtenprobleem?"</string>
<string name="page_broken" msgid="2968770793669433462">"Pagina van het pdf-document kan niet worden geladen"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Onvoldoende gegevens om het pdf-document te verwerken"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-or/strings.xml b/pdf/pdf-viewer/src/main/res/values-or/strings.xml
index 26a54de..66b7f68 100644
--- a/pdf/pdf-viewer/src/main/res/values-or/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-or/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ଫାଇଲ୍ର ପ୍ରକାର"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ଏହି ଫାଇଲ ସୁରକ୍ଷିତ ହୋଇଛି"</string>
<string name="label_password_first" msgid="4456258714097111908">"ପାସୱାର୍ଡ"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"ପାସୱାର୍ଡ ଭୁଲ ଅଛି"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"ଜୁମ <xliff:g id="FIRST">%1$d</xliff:g> ଶତକଡ଼ା"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> ପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ"</string>
<string name="desc_page" msgid="5684226167093594168">"ପୃଷ୍ଠା <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"ଆପଣଙ୍କର ମନ୍ତବ୍ୟ ଦେବା ପାଇଁ ଟେକ୍ସଟ ଚୟନ କରନ୍ତୁ"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"ମନ୍ତବ୍ୟ ଦେବା ପାଇଁ ଏକ ଏରିଆରେ ଟାପ କରନ୍ତୁ"</string>
- <string name="action_cancel" msgid="5494417739210197522">"ବାତିଲ କରନ୍ତୁ"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF ଡିସପ୍ଲେ ହୋଇପାରିବ ନାହିଁ (<xliff:g id="TITLE">%1$s</xliff:g>ର ଫର୍ମାଟ ଅବୈଧ ଅଟେ)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g> ପୃଷ୍ଠା ଡିସପ୍ଲେ ହୋଇପାରିବ ନାହିଁ (ଫାଇଲରେ ତ୍ରୁଟି)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"ଏହି ଆଇଟମ ପାଇଁ ଏନୋଟେସନ ମୋଡ ଲୋଡ କରାଯାଇପାରିବ ନାହିଁ।"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"ଲିଙ୍କ: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>ରେ ଥିବା ୱେବପୃଷ୍ଠା"</string>
<string name="desc_web_link" msgid="2776023299237058419">"ଲିଙ୍କ: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ଇମେଲ୍: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g>ର <xliff:g id="FIRST">%1$d</xliff:g>ରୁ <xliff:g id="LAST">%2$d</xliff:g> ପୃଷ୍ଠା"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"ଇମେଜ: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ଫାଇଲରେ ଖୋଜନ୍ତୁ"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"କୌଣସି ମେଳ ମିଳୁ ନାହିଁ।"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"ପୂର୍ବବର୍ତ୍ତୀ"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"ପରବର୍ତ୍ତୀ"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ଫାଇଲକୁ ଏଡିଟ କରନ୍ତୁ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ଅନଲକ କରିବା ପାଇଁ ପାସୱାର୍ଡ ଲେଖନ୍ତୁ"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ଫାଇଲ ଖୋଲିବାରେ ବିଫଳ ହୋଇଛି। ସମ୍ଭାବ୍ୟ ଅନୁମତି ସମସ୍ୟା ଅଛି?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF ଡକ୍ୟୁମେଣ୍ଟ ପାଇଁ ପୃଷ୍ଠା ବିଭାଜିତ ହୋଇଛି"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ଡକ୍ୟୁମେଣ୍ଟ ପ୍ରକ୍ରିୟାକରଣ ପାଇଁ ପର୍ଯ୍ୟାପ୍ତ ଡାଟା ନାହିଁ"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pa/strings.xml b/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
index 876f5ae..8d4c105 100644
--- a/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ਫ਼ਾਈਲ ਦੀ ਕਿਸਮ"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ਇਹ ਫ਼ਾਈਲ ਸੁਰੱਖਿਅਤ ਹੈ"</string>
<string name="label_password_first" msgid="4456258714097111908">"ਪਾਸਵਰਡ"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"ਪਾਸਵਰਡ ਗਲਤ ਹੈ"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"ਜ਼ੂਮ <xliff:g id="FIRST">%1$d</xliff:g> ਫ਼ੀਸਦ"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> ਪੰਨੇ \'ਤੇ ਜਾਓ"</string>
<string name="desc_page" msgid="5684226167093594168">"ਪੰਨਾ <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"ਆਪਣੀ ਟਿੱਪਣੀ ਕਰਨ ਲਈ ਲਿਖਤ ਨੂੰ ਚੁਣੋ"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"ਟਿੱਪਣੀ ਕਰਨ ਲਈ ਕਿਸੇ ਖੇਤਰ \'ਤੇ ਟੈਪ ਕਰੋ"</string>
- <string name="action_cancel" msgid="5494417739210197522">"ਰੱਦ ਕਰੋ"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF ਨੂੰ ਦਿਖਾਇਆ ਨਹੀਂ ਜਾ ਸਕਦਾ (<xliff:g id="TITLE">%1$s</xliff:g> ਅਵੈਧ ਫਾਰਮੈਟ ਦਾ ਹੈ)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g> ਪੰਨੇ ਨੂੰ ਦਿਖਾਇਆ ਨਹੀਂ ਜਾ ਸਕਦਾ (ਫ਼ਾਈਲ ਗੜਬੜ)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"ਇਸ ਆਈਟਮ ਲਈ ਐਨੋਟੇਸ਼ਨ ਮੋਡ ਨੂੰ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"ਲਿੰਕ: ਵੈੱਬ-ਪੰਨਾ <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> \'ਤੇ ਹੈ"</string>
<string name="desc_web_link" msgid="2776023299237058419">"ਲਿੰਕ: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ਈਮੇਲ: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"ਕੁੱਲ <xliff:g id="TOTAL">%3$d</xliff:g> ਵਿੱਚੋਂ <xliff:g id="FIRST">%1$d</xliff:g> ਤੋਂ <xliff:g id="LAST">%2$d</xliff:g> ਤੱਕ ਪੰਨੇ"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"ਚਿੱਤਰ: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ਫ਼ਾਈਲ ਵਿੱਚ ਲੱਭੋ"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"ਕੋਈ ਮੇਲ ਨਹੀਂ ਮਿਲਿਆ।"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"ਪਿੱਛੇ"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"ਅੱਗੇ"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"ਬੰਦ ਕਰੋ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ਫ਼ਾਈਲ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਪਾਸਵਰਡ ਦਾਖਲ ਕਰੋ"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ਫ਼ਾਈਲ ਨੂੰ ਖੋਲ੍ਹਣਾ ਅਸਫਲ ਰਿਹਾ। ਕੀ ਸੰਭਵ ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਸਮੱਸਿਆ ਹੈ?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF ਦਸਤਾਵੇਜ਼ ਲਈ ਪੰਨਾ ਲੋਡ ਨਹੀਂ ਹੋ ਰਿਹਾ"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ਦਸਤਾਵੇਜ਼ \'ਤੇ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਲਈ ਲੋੜੀਂਦਾ ਡਾਟਾ ਨਹੀਂ ਹੈ"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pl/strings.xml b/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
index f791b6d..6583cb5 100644
--- a/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Typ pliku"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Ten plik jest chroniony"</string>
<string name="label_password_first" msgid="4456258714097111908">"Hasło"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Nieprawidłowe hasło"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"powiększenie <xliff:g id="FIRST">%1$d</xliff:g> procent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Idź do strony <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"strona <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Wybierz tekst, który chcesz skomentować"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Kliknij obszar, który chcesz skomentować"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Anuluj"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Nie można wyświetlić PDF-a (<xliff:g id="TITLE">%1$s</xliff:g> ma zły format)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Nie można wyświetlić strony <xliff:g id="PAGE">%1$d</xliff:g> (błąd pliku)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Nie można wczytać trybu adnotacji dla tego elementu."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: strona internetowa w domenie <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-mail: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"strony od <xliff:g id="FIRST">%1$d</xliff:g> do <xliff:g id="LAST">%2$d</xliff:g> z <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Obraz: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Znajdź w pliku"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Brak wyników."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Wstecz"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Dalej"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Zamknij"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Edytuj plik"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Podaj hasło, aby odblokować"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Nie udało się otworzyć pliku. Może to przez problem z uprawnieniami?"</string>
<string name="page_broken" msgid="2968770793669433462">"Strona w dokumencie PDF jest uszkodzona"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Brak wystarczającej ilości danych do przetworzenia dokumentu PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
index 7e0ddc0..e8beb95 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Tipo de arquivo"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Este arquivo está protegido"</string>
<string name="label_password_first" msgid="4456258714097111908">"Senha"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Senha incorreta"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom de <xliff:g id="FIRST">%1$d</xliff:g>%%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ir para a página <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"página <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Selecione o texto para inserir seu comentário"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Toque em uma área para comentar"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancelar"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Não é possível mostrar o PDF (<xliff:g id="TITLE">%1$s</xliff:g>: formato inválido)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Não é possível mostrar a página <xliff:g id="PAGE">%1$d</xliff:g> (erro de arquivo)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Não foi possível carregar o modo de anotação deste item."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: página da Web de <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-mail: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"páginas <xliff:g id="FIRST">%1$d</xliff:g> a <xliff:g id="LAST">%2$d</xliff:g> de <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imagem: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Localizar no arquivo"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nenhum resultado encontrado."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Anterior"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Próxima"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Fechar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar arquivo"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Digite a senha para desbloquear"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Falha ao abrir o arquivo. Possível problema de permissão?"</string>
<string name="page_broken" msgid="2968770793669433462">"Página do documento PDF corrompida"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Dados insuficientes para processamento do documento PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
index 53c34e4..2a1a80a 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Tipo de ficheiro"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Este ficheiro está protegido"</string>
<string name="label_password_first" msgid="4456258714097111908">"Palavra-passe"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Palavra-passe incorreta"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom a <xliff:g id="FIRST">%1$d</xliff:g> por cento"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ir para a página <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"página <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Selecione o texto para adicionar o comentário"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Toque numa área para comentar"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancelar"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Não é possível apresentar o PDF (<xliff:g id="TITLE">%1$s</xliff:g> tem um formato inválido)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Não é possível apresentar a página <xliff:g id="PAGE">%1$d</xliff:g> (erro de ficheiro)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Não é possível carregar o modo de anotação para este item."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: página Web em <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"páginas <xliff:g id="FIRST">%1$d</xliff:g> a <xliff:g id="LAST">%2$d</xliff:g> de <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imagem: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Procure no ficheiro"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Sem correspondências."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Anterior"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Seguinte"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Fechar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar ficheiro"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Introduza a palavra-passe para desbloquear"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"Falha ao abrir o ficheiro. Possível problema de autorização?"</string>
<string name="page_broken" msgid="2968770793669433462">"Página danificada para o documento PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Dados insuficientes para processar o documento PDF"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"Não é possível abrir o ficheiro PDF"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
index 7e0ddc0..e8beb95 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Tipo de arquivo"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Este arquivo está protegido"</string>
<string name="label_password_first" msgid="4456258714097111908">"Senha"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Senha incorreta"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom de <xliff:g id="FIRST">%1$d</xliff:g>%%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ir para a página <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"página <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Selecione o texto para inserir seu comentário"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Toque em uma área para comentar"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Cancelar"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Não é possível mostrar o PDF (<xliff:g id="TITLE">%1$s</xliff:g>: formato inválido)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Não é possível mostrar a página <xliff:g id="PAGE">%1$d</xliff:g> (erro de arquivo)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Não foi possível carregar o modo de anotação deste item."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: página da Web de <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-mail: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"páginas <xliff:g id="FIRST">%1$d</xliff:g> a <xliff:g id="LAST">%2$d</xliff:g> de <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imagem: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Localizar no arquivo"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nenhum resultado encontrado."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Anterior"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Próxima"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Fechar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar arquivo"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Digite a senha para desbloquear"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Falha ao abrir o arquivo. Possível problema de permissão?"</string>
<string name="page_broken" msgid="2968770793669433462">"Página do documento PDF corrompida"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Dados insuficientes para processamento do documento PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ro/strings.xml b/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
index 06344cf..ef6b73d 100644
--- a/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Tip de fișier"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Acest fișier este protejat"</string>
<string name="label_password_first" msgid="4456258714097111908">"Parolă"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Parolă incorectă"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zoom de <xliff:g id="FIRST">%1$d</xliff:g> %%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Accesează pagina <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"pagina <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Selectează textul pentru a comenta"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Atinge o zonă pentru a comenta"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Anulează"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Nu se poate afișa ca PDF (<xliff:g id="TITLE">%1$s</xliff:g> are un format nevalid)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Nu se poate afișa pagina <xliff:g id="PAGE">%1$d</xliff:g> (eroare de fișier)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Nu se poate încărca modul de adnotare pentru acest element."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: pagina web de la <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-mail: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"paginile <xliff:g id="FIRST">%1$d</xliff:g> – <xliff:g id="LAST">%2$d</xliff:g> din <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imagine: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Găsește în fișier"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nicio potrivire găsită."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Înapoi"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Înainte"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Închide"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Editează fișierul"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Introdu parola pentru a debloca"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Nu s-a putut deschide fișierul. Există vreo problemă cu permisiunile?"</string>
<string name="page_broken" msgid="2968770793669433462">"Pagină deteriorată pentru documentul PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Date insuficiente pentru procesarea documentului PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ru/strings.xml b/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
index 7f1fe6b..665e7cb 100644
--- a/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Тип файла"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Файл защищен паролем"</string>
<string name="label_password_first" msgid="4456258714097111908">"Пароль"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Неверный пароль"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"масштаб: <xliff:g id="FIRST">%1$d</xliff:g> %%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Перейти на страницу <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"страница <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Чтобы добавить комментарий, выделите текст."</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Чтобы добавить комментарий, коснитесь области."</string>
- <string name="action_cancel" msgid="5494417739210197522">"Отмена"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Недопустимый формат файла \"<xliff:g id="TITLE">%1$s</xliff:g>\"."</string>
<string name="error_on_page" msgid="1592475819957182385">"Невозможно показать страницу <xliff:g id="PAGE">%1$d</xliff:g> (ошибка файла)."</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Не удалось загрузить режим заметок для этого объекта."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Ссылка: страница на сайте <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Ссылка: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Адрес электронной почты: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"страницы <xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> из <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Изображение: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Найти в файле"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Ничего не найдено."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Назад"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Далее"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Закрыть"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> из <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Редактировать файл"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Введите пароль для разблокировки."</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Не удалось открыть файл. Возможно, нет необходимых разрешений."</string>
<string name="page_broken" msgid="2968770793669433462">"Страница документа PDF повреждена"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Недостаточно данных для обработки документа PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-si/strings.xml b/pdf/pdf-viewer/src/main/res/values-si/strings.xml
index 54c42c3..dc5d075 100644
--- a/pdf/pdf-viewer/src/main/res/values-si/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-si/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ගොනු වර්ගය"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"මෙම ගොනුව ආරක්ෂා කර ඇත"</string>
<string name="label_password_first" msgid="4456258714097111908">"මුරපදය"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"මුරපදය වැරදියි"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"විශාලනය සියයට <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> වෙනි පිටුවට යන්න"</string>
<string name="desc_page" msgid="5684226167093594168">"පිටුව <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"ඔබේ අදහස තැබීමට පෙළ තෝරන්න"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"අදහස් දැක්වීමට ප්රදේශයක් තට්ටු කරන්න"</string>
- <string name="action_cancel" msgid="5494417739210197522">"අවලංගු කරන්න"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF සංදර්ශනය කළ නොහැක (<xliff:g id="TITLE">%1$s</xliff:g> අවලංගු ආකෘතියකි)"</string>
<string name="error_on_page" msgid="1592475819957182385">"පිටුව සංදර්ශනය කළ නොහැක <xliff:g id="PAGE">%1$d</xliff:g> (ගොනු දෝෂය)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"මෙම අයිතමය සඳහා අනුසටහන් ප්රකාරය පූරණය කළ නොහැක."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"සබැඳිය: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> හි දී වෙබ් පිටුව"</string>
<string name="desc_web_link" msgid="2776023299237058419">"සබැඳිය: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ඉ-තැපෑල: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"පිටු <xliff:g id="TOTAL">%3$d</xliff:g>න් <xliff:g id="FIRST">%1$d</xliff:g> සිට <xliff:g id="LAST">%2$d</xliff:g> දක්වා"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"රූපය: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ගොනුව සොයා ගන්න"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"කිසි ගැළපීමක් හමු නොවිය."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"පෙර"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"මීළඟ"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"වසන්න"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ගොනුව සංස්කරණ කරන්න"</string>
<string name="password_not_entered" msgid="8875370870743585303">"අගුලු හැරීමට මුරපදය ඇතුළත් කරන්න"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ගොනුව විවෘත කිරීමට අසමත් විය. අවසර ගැටලුවක් විය හැකි ද?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF ලේඛනය සඳහා පිටුව හානි වී ඇත"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ලේඛනය සැකසීම සඳහා ප්රමාණවත් දත්ත නොමැත"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sk/strings.xml b/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
index f2886cd..d44c7b6 100644
--- a/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Typ súboru"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Tento súbor je chránený"</string>
<string name="label_password_first" msgid="4456258714097111908">"Heslo"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Heslo je nesprávne"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"lupa na <xliff:g id="FIRST">%1$d</xliff:g> percent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Prejsť na stránku <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"strana <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Vyberte text, kde chcete umiestniť komentár"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Klep. na oblasť, ktorú chcete komentovať"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Zrušiť"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Súbor PDF sa nedá zobraziť (<xliff:g id="TITLE">%1$s</xliff:g> má neplatný formát)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g>. strana sa nedá zobraziť (chyba súboru)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Pre túto položku sa nedá načítať režim anotácií."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Odkaz: webová stránka v doméne <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Odkaz: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E‑mail: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"strany od <xliff:g id="FIRST">%1$d</xliff:g> do <xliff:g id="LAST">%2$d</xliff:g> z <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Obrázok: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Vyhľadajte v súbore"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Neboli nájdené žiadne zhody."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Naspäť"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Ďalej"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Zavrieť"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Upraviť súbor"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Zadajte heslo na odomknutie"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Súbor sa nepodarilo otvoriť. Možno sa vyskytol problém s povolením."</string>
<string name="page_broken" msgid="2968770793669433462">"Stránka sa v dokumente vo formáte PDF nedá načítať"</string>
<string name="needs_more_data" msgid="3520133467908240802">"V dokumente vo formáte PDF nie je dostatok údajov na spracovanie"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sl/strings.xml b/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
index 457b9f1..afef99a 100644
--- a/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Vrsta datoteke"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Ta datoteka je zaščitena"</string>
<string name="label_password_first" msgid="4456258714097111908">"Geslo"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Napačno geslo"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"povečava <xliff:g id="FIRST">%1$d</xliff:g> %%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Pojdi na stran <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"stran <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Izberite besedilo, da umestite komentar"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Dotaknite se, kjer želite komentirati"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Prekliči"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF-ja ni mogoče prikazati (<xliff:g id="TITLE">%1$s</xliff:g> ni veljavna oblika)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Strani <xliff:g id="PAGE">%1$d</xliff:g> ni mogoče prikazati (napaka v datoteki)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Načina opomb za ta element ni mogoče naložiti."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Povezava: spletna stran v domeni <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Povezava: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-poštni naslov: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"strani <xliff:g id="FIRST">%1$d</xliff:g> do <xliff:g id="LAST">%2$d</xliff:g> od <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Slika: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Iskanje v datoteki"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Ni rezultatov."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Nazaj"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Naprej"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Zapri"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Urejanje datoteke"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Vnesite geslo za odklepanje"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"Odpiranje datoteke ni uspelo. Ali morda gre za težavo z dovoljenjem?"</string>
<string name="page_broken" msgid="2968770793669433462">"Strani iz dokumenta PDF ni mogoče prikazati"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nezadostni podatki za obdelavo dokumenta PDF"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"Datoteke PDF ni mogoče odpreti"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sq/strings.xml b/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
index ece0da4..37347d3 100644
--- a/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Lloji i skedarit"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Ky skedar është i mbrojtur."</string>
<string name="label_password_first" msgid="4456258714097111908">"Fjalëkalimi"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Fjalëkalimi është i pasaktë"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zmadho me <xliff:g id="FIRST">%1$d</xliff:g> për qind"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Shko te faqja <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"faqja <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Zgjidh tekstin për të vendosur komentin"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Trokit te një zonë për të komentuar"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Anulo"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Skedari PDF nuk mund të shfaqet (\"<xliff:g id="TITLE">%1$s</xliff:g>\" ka format të pavlefshëm)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Nuk mund të shfaqet faqja <xliff:g id="PAGE">%1$d</xliff:g> (gabim i skedarit)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Modaliteti i shënimit nuk mund të ngarkohet për këtë artikull."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Lidhja: faqe uebi te <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Lidhja: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email-i: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"faqet nga <xliff:g id="FIRST">%1$d</xliff:g> deri në <xliff:g id="LAST">%2$d</xliff:g> nga <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imazhi: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Gjej te skedari"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Nuk u gjetën përputhje."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Pas"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Para"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Mbyll"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Modifiko skedarin"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Fut fjalëkalimin për ta shkyçur"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Hapja e skedarit dështoi. Problem i mundshëm me lejet?"</string>
<string name="page_broken" msgid="2968770793669433462">"Faqe e dëmtuar për dokumentin PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Të dhëna të pamjaftueshme për përpunimin e dokumentit PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sr/strings.xml b/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
index e8b8b9c..bcab510 100644
--- a/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Тип фајла"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Овај фајл је заштићен"</string>
<string name="label_password_first" msgid="4456258714097111908">"Лозинка"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Лозинка је нетачна"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"проценат зума: <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Иди на страницу <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"<xliff:g id="PAGE">%1$d</xliff:g>. страница"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Изаберите текст за постављање коментара"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Додирните област за коментарисање"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Откажи"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF не може да се прикаже (<xliff:g id="TITLE">%1$s</xliff:g> има неважећи формат)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Страница <xliff:g id="PAGE">%1$d</xliff:g> не може да се прикаже (грешка у фајлу)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Учитавање режима напомена за ову ставку није успело."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Линк: веб-страница на <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Линк: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Имејл: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"странице <xliff:g id="FIRST">%1$d</xliff:g>. до <xliff:g id="LAST">%2$d</xliff:g>. од <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Слика: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Пронађите у фајлу"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Није пронађено ниједно подударање."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Претходно"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Даље"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Затвори"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Измени фајл"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Унесите лозинку за откључавање"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Отварање фајла није успело. Можда постоје проблеми са дозволом?"</string>
<string name="page_broken" msgid="2968770793669433462">"Неисправна страница за PDF документ"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Недовољно података за обраду PDF документа"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sv/strings.xml b/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
index 90ee5c1..3fa349f 100644
--- a/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Filtyp"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Den här filen är skyddad"</string>
<string name="label_password_first" msgid="4456258714097111908">"Lösenord"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Felaktigt lösenord"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zooma <xliff:g id="FIRST">%1$d</xliff:g> procent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Öppna sidan <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"sida <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Markera text för att kommentera"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Tryck på ett område för att kommentera"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Avbryt"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Det går inte att visa PDF-filen (<xliff:g id="TITLE">%1$s</xliff:g> har ett ogiltigt format)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Det går inte att visa sidan <xliff:g id="PAGE">%1$d</xliff:g> (filfel)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Det gick inte att läsa in kommentarsläget för det här objektet."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Länk: webbsida på <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Länk: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-postadress: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"sidorna <xliff:g id="FIRST">%1$d</xliff:g> till <xliff:g id="LAST">%2$d</xliff:g> av <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Bild: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Hitta i filen"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Inga matchningar hittades."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Föregående"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Nästa"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Stäng"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Redigera fil"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Ange lösenord för att låsa upp"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Det gick inte att öppna filen. Detta kan bero på ett behörighetsproblem."</string>
<string name="page_broken" msgid="2968770793669433462">"Det gick inte att läsa in en sida i PDF-dokumentet"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Otillräcklig data för att behandla PDF-dokumentet"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sw/strings.xml b/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
index 2219b10..abf9c4b5 100644
--- a/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Aina ya faili"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Faili hii inalindwa"</string>
<string name="label_password_first" msgid="4456258714097111908">"Nenosiri"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Nenosiri si sahihi"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"kuza kwa asilimia <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Fungua ukurasa wa <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"ukurasa wa <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Chagua maandishi ili utoe maoni yako"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Gusa sehemu ili utoe maoni kuihusu"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Acha"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Imeshindwa kuonyesha PDF (muundo wa <xliff:g id="TITLE">%1$s</xliff:g> si sahihi)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Imeshindwa kuonyesha ukurasa wa <xliff:g id="PAGE">%1$d</xliff:g> (hitilafu ya faili)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Imeshindwa kupakia hali ya vidokezo kwenye kipengee hiki."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Kiungo: ukurasa wa wavuti katika <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Kiungo: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Anwani ya Barua Pepe: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"ukurasa wa <xliff:g id="FIRST">%1$d</xliff:g> hadi <xliff:g id="LAST">%2$d</xliff:g> kati ya <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Picha: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Tafuta kwenye faili"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Hakuna vipengee vinavyolingana."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Iliyotangulia"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Endelea"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Funga"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> kati ya <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Badilisha faili"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Weka nenosiri ili ufungue"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Imeshindwa kufungua faili. Je, linaweza kuwa tatizo la ruhusa?"</string>
<string name="page_broken" msgid="2968770793669433462">"Ukurasa wa hati ya PDF una tatizo"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Hamna data ya kutosha kuchakata hati ya PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ta/strings.xml b/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
index 4c28feb..2d1d291 100644
--- a/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ஃபைல் வகை"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"இந்த ஃபைல் பாதுகாக்கப்பட்டுள்ளது"</string>
<string name="label_password_first" msgid="4456258714097111908">"கடவுச்சொல்"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"கடவுச்சொல் தவறானது"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> சதவீத அளவை மாற்று"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"பக்கம் <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>க்கு செல்லும்"</string>
<string name="desc_page" msgid="5684226167093594168">"பக்கம் <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"கருத்து வழங்க வார்த்தையைத் தேர்ந்தெடுக்கவும்"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"கருத்து வழங்குவதற்கான பகுதியைத் தட்டவும்"</string>
- <string name="action_cancel" msgid="5494417739210197522">"ரத்துசெய்"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDFஐக் காட்ட முடியவில்லை (<xliff:g id="TITLE">%1$s</xliff:g> தவறான வடிவத்தில் உள்ளது)"</string>
<string name="error_on_page" msgid="1592475819957182385">"பக்கம் <xliff:g id="PAGE">%1$d</xliff:g>ஐக் காட்ட முடியவில்லை (ஃபைல் பிழை)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"இதற்கான விரிவுரைப் பயன்முறையை ஏற்ற முடியவில்லை."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"இணைப்பு: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> இல் உள்ள இணையப் பக்கம்"</string>
<string name="desc_web_link" msgid="2776023299237058419">"இணைப்பு: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"மின்னஞ்சல்: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g> பக்கங்களில் <xliff:g id="FIRST">%1$d</xliff:g>முதல் <xliff:g id="LAST">%2$d</xliff:g> வரை"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"படம்: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ஃபைலில் தேடுக"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"பொருத்தங்கள் கண்டறியப்படவில்லை."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"முந்தையதற்குச் செல்லும்"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"அடுத்ததற்குச் செல்லும்"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"மூடும்"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ஃபைலைத் திருத்து"</string>
<string name="password_not_entered" msgid="8875370870743585303">"அன்லாக் செய்ய கடவுச்சொல்லை டைப் செய்யவும்"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"ஃபைலைத் திறக்க முடியவில்லை. அனுமதி தொடர்பான சிக்கல் உள்ளதா?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF ஆவணத்தை ஏற்ற முடியவில்லை"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ஆவணத்தைச் செயலாக்குவதற்குப் போதுமான தரவு இல்லை"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-te/strings.xml b/pdf/pdf-viewer/src/main/res/values-te/strings.xml
index 26c2954..ffcee7a 100644
--- a/pdf/pdf-viewer/src/main/res/values-te/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-te/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ఫైల్ రకం"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ఈ ఫైల్ సంరక్షించబడుతోంది"</string>
<string name="label_password_first" msgid="4456258714097111908">"పాస్వర్డ్"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"పాస్వర్డ్ తప్పు"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"జూమ్ <xliff:g id="FIRST">%1$d</xliff:g> శాతం"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> పేజీకి వెళ్లండి"</string>
<string name="desc_page" msgid="5684226167093594168">"పేజీ <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"మీరు కామెంట్ చేయడానికి టెక్స్ట్ను ఎంచుకోండి"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"ఎక్కడ కామెంట్ చేయాలో ట్యాప్ చేయండి"</string>
- <string name="action_cancel" msgid="5494417739210197522">"రద్దు చేయండి"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF డిస్ప్లే చేయడం సాధ్యం కాదు (<xliff:g id="TITLE">%1$s</xliff:g> చెల్లని ఫార్మాట్లో ఉంది)"</string>
<string name="error_on_page" msgid="1592475819957182385">"పేజీని డిస్ప్లే చేయడం సాధ్యం కాదు <xliff:g id="PAGE">%1$d</xliff:g> (ఫైల్ ఎర్రర్)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"ఈ ఐటెమ్ కోసం అదనపు గమనిక మోడ్ను లోడ్ చేయడం సాధ్యం కాదు."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"లింక్: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>లో వెబ్ పేజీ"</string>
<string name="desc_web_link" msgid="2776023299237058419">"లింక్: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ఈమెయిల్: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g>లో <xliff:g id="FIRST">%1$d</xliff:g> నుండి <xliff:g id="LAST">%2$d</xliff:g> పేజీలు"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"ఇమేజ్: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ఫైల్లో కనుగొనండి"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"మ్యాచ్లు ఏవీ దొరకలేదు."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"మునుపటి"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"తర్వాత"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"మూసివేయండి"</string>
<string name="message_match_status" msgid="6288242289981639727">"మొత్తం <xliff:g id="TOTAL">%2$d</xliff:g>లో <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"ఫైల్ను ఎడిట్ చేయండి"</string>
<string name="password_not_entered" msgid="8875370870743585303">"అన్లాక్ చేయడానికి పాస్వర్డ్ను నమోదు చేయండి"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"ఫైల్ను తెరవడం విఫలమైంది. అనుమతికి సంబంధించిన సమస్య కావచ్చా?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF డాక్యుమెంట్కు సంబంధించి పేజీ బ్రేక్ అయింది"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF డాక్యుమెంట్ను ప్రాసెస్ చేయడానికి డేటా తగినంత లేదు"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ఫైల్ను తెరవడం సాధ్యపడదు"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-th/strings.xml b/pdf/pdf-viewer/src/main/res/values-th/strings.xml
index 4565375..be31750 100644
--- a/pdf/pdf-viewer/src/main/res/values-th/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-th/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"ประเภทไฟล์"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"ไฟล์นี้ได้รับการป้องกัน"</string>
<string name="label_password_first" msgid="4456258714097111908">"รหัสผ่าน"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"รหัสผ่านไม่ถูกต้อง"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"ซูม <xliff:g id="FIRST">%1$d</xliff:g> เปอร์เซ็นต์"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"ไปที่หน้า <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"หน้า <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"เลือกข้อความเพื่อใส่ความคิดเห็น"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"แตะบริเวณที่ต้องการแสดงความคิดเห็น"</string>
- <string name="action_cancel" msgid="5494417739210197522">"ยกเลิก"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"ไม่สามารถแสดง PDF (<xliff:g id="TITLE">%1$s</xliff:g> มีรูปแบบไม่ถูกต้อง)"</string>
<string name="error_on_page" msgid="1592475819957182385">"ไม่สามารถแสดงหน้า <xliff:g id="PAGE">%1$d</xliff:g> (ไฟล์มีข้อผิดพลาด)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"โหลดโหมดคําอธิบายประกอบสําหรับรายการนี้ไม่ได้"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"ลิงก์: หน้าเว็บที่ <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"ลิงก์: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"อีเมล: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"หน้า <xliff:g id="FIRST">%1$d</xliff:g> ถึง <xliff:g id="LAST">%2$d</xliff:g> จาก <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"รูปภาพ: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ค้นหาในไฟล์"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"ไม่พบข้อมูลที่ตรงกัน"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"ก่อนหน้า"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"ถัดไป"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"ปิด"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"แก้ไขไฟล์"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ป้อนรหัสผ่านเพื่อปลดล็อก"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"เปิดไฟล์ไม่สำเร็จ อาจเกิดจากปัญหาด้านสิทธิ์"</string>
<string name="page_broken" msgid="2968770793669433462">"หน้าในเอกสาร PDF เสียหาย"</string>
<string name="needs_more_data" msgid="3520133467908240802">"ข้อมูลไม่เพียงพอสำหรับการประมวลผลเอกสาร PDF"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"เปิดไฟล์ PDF ไม่ได้"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-tl/strings.xml b/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
index 6399462..5b20647 100644
--- a/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Uri ng file"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Pinoprotektahan ang file na ito"</string>
<string name="label_password_first" msgid="4456258714097111908">"Password"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Mali ang password"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"i-zoom nang <xliff:g id="FIRST">%1$d</xliff:g> porsyento"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Pumunta sa page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"page <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Pumili ng text para ilagay ang iyong komento"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Mag-tap ng bahaging kokomentuhan"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Kanselahin"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Hindi maipakita ang PDF (invalid ang format ng <xliff:g id="TITLE">%1$s</xliff:g>)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Hindi maipakita ang page <xliff:g id="PAGE">%1$d</xliff:g> (error sa file)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Hindi ma-load ang annotation mode para sa item na ito."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Link: webpage sa <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Link: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"page <xliff:g id="FIRST">%1$d</xliff:g> hanggang <xliff:g id="LAST">%2$d</xliff:g> sa <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Larawan: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Maghanap sa file"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Walang nahanap na tugma."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Nakaraan"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Susunod"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Isara"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"I-edit ang file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Ilagay ang password para i-unlock"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"Hindi nabuksan ang file. Baka may isyu sa pahintulot?"</string>
<string name="page_broken" msgid="2968770793669433462">"Sira ang page para sa PDF na dokumento"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Kulang ang data para maproseso ang PDF na dokumento"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"Hindi mabuksan ang PDF file"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-tr/strings.xml b/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
index c306300..8ad6bf2 100644
--- a/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Dosya türü"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Bu dosya korunuyor"</string>
<string name="label_password_first" msgid="4456258714097111908">"Şifre"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Şifre yanlış"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"yakınlaştırma yüzdesi <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Şu sayfaya git <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"sayfa <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Yorumunuzu yerleştireceğiniz metni seçin"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Hakkında yorum yapacağınız alana dokunun"</string>
- <string name="action_cancel" msgid="5494417739210197522">"İptal"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF görüntülenemiyor (<xliff:g id="TITLE">%1$s</xliff:g> geçersiz biçimde)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g>. sayfa görüntülenemiyor (dosya hatası)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Bu öğe için ek açıklama modu yüklenemiyor."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Bağlantı: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> alan adındaki web sayfası"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Bağlantı: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"E-posta gönder: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"sayfa <xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Resim: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Dosyada bul"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Eşleşme bulunamadı."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Önceki"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Sonraki"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Kapat"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Dosyayı düzenle"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Kilidi açmak için şifreyi girin"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Dosya açılamadı. İzin sorunundan kaynaklanıyor olabilir mi?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF dokümanının sayfası bozuk"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF dokümanını işleyecek kadar yeterli veri yok"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-uk/strings.xml b/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
index 8585b92..c381f20 100644
--- a/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Тип файлу"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Цей файл захищено"</string>
<string name="label_password_first" msgid="4456258714097111908">"Пароль"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Неправильний пароль"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"масштаб <xliff:g id="FIRST">%1$d</xliff:g>%%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Перейти на сторінку <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"сторінка <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Виділіть текст, щоб додати коментар"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Торкніться місця, яке хочете коментувати"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Скасувати"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Не вдається відобразити PDF (недійсний формат файлу \"<xliff:g id="TITLE">%1$s</xliff:g>\")"</string>
<string name="error_on_page" msgid="1592475819957182385">"Не вдається відобразити сторінку <xliff:g id="PAGE">%1$d</xliff:g> (помилка файлу)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Не вдається завантажити режим анотацій для цього об’єкта."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Посилання: вебсторінка в домені <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Посилання: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Електронна адреса: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"сторінки <xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> з <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Зображення: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Пошук у файлі"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Нічого не знайдено."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Назад"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Далі"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Закрити"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Редагувати файл"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Введіть пароль, щоб розблокувати"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Не вдалося відкрити файл. Можливо, виникла проблема з дозволом."</string>
<string name="page_broken" msgid="2968770793669433462">"Сторінку документа PDF пошкоджено"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Недостатньо даних для обробки документа PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ur/strings.xml b/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
index 10a6c0a..6027e36 100644
--- a/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"فائل کی قسم"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"یہ فائل محفوظ ہے"</string>
<string name="label_password_first" msgid="4456258714097111908">"پاس ورڈ"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"پاس ورڈ غلط ہے"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"زوم <xliff:g id="FIRST">%1$d</xliff:g> فیصد"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> صفحہ پر جائیں"</string>
<string name="desc_page" msgid="5684226167093594168">"صفحہ <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"اپنا تبصرہ شامل کرنے کیلئے ٹیکسٹ منتخب کریں"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"تبصرہ کرنے کے لیے کسی حصہ پر تھپھتپائیں"</string>
- <string name="action_cancel" msgid="5494417739210197522">"منسوخ کریں"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF ڈسپلے نہیں کر سکتا (<xliff:g id="TITLE">%1$s</xliff:g> کا فارمیٹ غلط ہے)"</string>
<string name="error_on_page" msgid="1592475819957182385">"صفحہ <xliff:g id="PAGE">%1$d</xliff:g> ڈسپلے نہیں کر سکتا (فائل کی خرابی)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"اس آئٹم کے لیے تشریح موڈ لوڈ نہیں ہو سکتا۔"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"لنک: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> پر ویب صفحہ"</string>
<string name="desc_web_link" msgid="2776023299237058419">"لنک: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"ای میل: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"صفحات <xliff:g id="FIRST">%1$d</xliff:g> سے <xliff:g id="LAST">%2$d</xliff:g> از <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"تصویر: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"فائل میں تلاش کریں"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"کسی مماثلت کا پتا نہیں چلا۔"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"پچھلا"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"اگلا"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"بند کریں"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="TOTAL">%2$d</xliff:g> / <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"فائل میں ترمیم کریں"</string>
<string name="password_not_entered" msgid="8875370870743585303">"غیر مقفل کرنے کیلئے پاس ورڈ درج کریں"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"فائل کھولنے میں ناکام۔ کیا یہ اجازت کا مسئلہ ہو سکتا ہے؟"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF دستاویز کیلئے شکستہ صفحہ"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF دستاویز پر کارروائی کرنے کیلئے ڈیٹا ناکافی ہے"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-uz/strings.xml b/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
index c313d09..41b89f8 100644
--- a/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Fayl turi"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Ushbu fayl himoyalangan"</string>
<string name="label_password_first" msgid="4456258714097111908">"Parol"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Parol xato"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"zum: <xliff:g id="FIRST">%1$d</xliff:g> foiz"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g>-sahifaga oʻtish"</string>
<string name="desc_page" msgid="5684226167093594168">"sahifa: <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Fikr bildirish uchun matnni tanlang."</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Fikr qoʻshish uchun biror maydonni bosing"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Bekor qilish"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"PDF fayl koʻrsatilmaydi (<xliff:g id="TITLE">%1$s</xliff:g> yaroqsiz formatda)"</string>
<string name="error_on_page" msgid="1592475819957182385">"<xliff:g id="PAGE">%1$d</xliff:g>-sahifa koʻrsatilmaydi (fayl xatosi)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Bu obyekt uchun izoh rejimi yuklanmadi."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Havola: <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> sahifasi"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Havola: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"sahifalar: <xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Tasvir: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Fayl ichidan topish"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Hech narsa topilmadi."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Avvalgisi"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Keyingisi"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Yopish"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Faylni tahrirlash"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Ochish uchun parolni kiriting"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"Fayl ochilmadi. Ruxsat bilan muammo bormi?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF hujjat sahifasi yaroqsiz"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF hujjatni qayta ishlash uchun kerakli axborotlar yetarli emas"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF fayk ochilmadi"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-v34/colors.xml b/pdf/pdf-viewer/src/main/res/values-v34/colors.xml
deleted file mode 100644
index 29c9a19..0000000
--- a/pdf/pdf-viewer/src/main/res/values-v34/colors.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<!--
- Copyright 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<resources>
-
- <color name="google_blue">#ff1a73e8</color>
- <color name="google_white">#ffffffff</color>
- <color name="google_grey">#ff3c4043</color>
- <color name="text_default">#666</color>
- <color name="text_error">#da4336</color>
- <color name="selection_handles">#00aadd</color>
- <color name="search_background">@android:color/system_surface_container_light</color>
- <color name="search_textbox">@android:color/system_surface_bright_light</color>
- <color name="search_texthint">@android:color/system_on_surface_variant_light</color>
- <color name="search_textColor">@android:color/system_on_surface_light</color>
- <color name="search_count">@android:color/system_on_surface_variant_light</color>
- <color name="search_prev_button">@android:color/system_on_surface_variant_light</color>
- <color name="search_next_button">@android:color/system_on_surface_variant_light</color>
- <color name="search_close_button">@android:color/system_on_surface_variant_light</color>
-
-
-</resources>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/values-vi/strings.xml b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
index ea11021..5f50634 100644
--- a/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Loại tệp"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Tệp này đang được bảo vệ"</string>
<string name="label_password_first" msgid="4456258714097111908">"Mật khẩu"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Mật khẩu không chính xác"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"thu phóng <xliff:g id="FIRST">%1$d</xliff:g> phần trăm"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Chuyển đến trang <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"trang <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Chọn văn bản để đưa ra nhận xét"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Nhấn vào một khu vực để thêm nhận xét"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Huỷ"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Không hiển thị được tệp PDF (<xliff:g id="TITLE">%1$s</xliff:g> có định dạng không hợp lệ)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Không hiển thị được trang <xliff:g id="PAGE">%1$d</xliff:g> (lỗi tệp)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Không tải được chế độ chú giải cho mục này."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Đường liên kết: trang trên trang web <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Đường liên kết: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"Email: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"các trang <xliff:g id="FIRST">%1$d</xliff:g> đến <xliff:g id="LAST">%2$d</xliff:g> trong số <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Hình ảnh: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Tìm trong tệp"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Không tìm thấy kết quả phù hợp."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Trước"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Tiếp theo"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Đóng"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Chỉnh sửa tệp"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Nhập mật khẩu để mở khoá"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Không mở được tệp này. Có thể là do vấn đề về quyền?"</string>
<string name="page_broken" msgid="2968770793669433462">"Tài liệu PDF này bị lỗi trang"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Không đủ dữ liệu để xử lý tài liệu PDF này"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-w840dp/dimens.xml b/pdf/pdf-viewer/src/main/res/values-w840dp/dimens.xml
deleted file mode 100644
index 077536c..0000000
--- a/pdf/pdf-viewer/src/main/res/values-w840dp/dimens.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2024 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<resources>
- <dimen name="viewer_doc_padding_x">80dp</dimen>
-</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
index aea5179..c6dbf7e 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"文件类型"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"此文件受密码保护"</string>
<string name="label_password_first" msgid="4456258714097111908">"密码"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"密码不正确"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"缩放比例为百分之 <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"前往第 <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> 页"</string>
<string name="desc_page" msgid="5684226167093594168">"第 <xliff:g id="PAGE">%1$d</xliff:g> 页"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"选择要添加评论的文本"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"点按要添加评论的区域"</string>
- <string name="action_cancel" msgid="5494417739210197522">"取消"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"无法显示 PDF(“<xliff:g id="TITLE">%1$s</xliff:g>”的格式无效)"</string>
<string name="error_on_page" msgid="1592475819957182385">"无法显示第 <xliff:g id="PAGE">%1$d</xliff:g> 页(文件错误)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"无法为此内容加载注解模式。"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"链接:位于 <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> 的网页"</string>
<string name="desc_web_link" msgid="2776023299237058419">"链接:<xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"电子邮件地址:<xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"第 <xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> 页,共 <xliff:g id="TOTAL">%3$d</xliff:g> 页"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"图片:<xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"在文件中查找"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"未找到匹配项。"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"上一页"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"下一页"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"关闭"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"编辑文件"</string>
<string name="password_not_entered" msgid="8875370870743585303">"请输入密码进行解锁"</string>
@@ -56,4 +53,5 @@
<string name="file_error" msgid="4003885928556884091">"无法打开文件。可能是由于权限问题导致?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF 文档的页面已损坏"</string>
<string name="needs_more_data" msgid="3520133467908240802">"数据不足,无法处理 PDF 文档"</string>
+ <string name="error_cannot_open_pdf" msgid="2361919778558145071">"无法打开 PDF 文件"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
index 8cff251..691bdfe 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"檔案類型"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"此檔案受到保護"</string>
<string name="label_password_first" msgid="4456258714097111908">"密碼"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"密碼不正確"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"縮放 <xliff:g id="FIRST">%1$d</xliff:g>%%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"前往第 <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> 頁"</string>
<string name="desc_page" msgid="5684226167093594168">"第 <xliff:g id="PAGE">%1$d</xliff:g> 頁"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"選取文字以加入留言"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"輕按要加入留言的區域"</string>
- <string name="action_cancel" msgid="5494417739210197522">"取消"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"無法顯示 PDF (<xliff:g id="TITLE">%1$s</xliff:g> 格式無效)"</string>
<string name="error_on_page" msgid="1592475819957182385">"無法顯示第 <xliff:g id="PAGE">%1$d</xliff:g> 頁 (檔案錯誤)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"無法為此項目載入註釋模式。"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"連結:位於 <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> 的網頁"</string>
<string name="desc_web_link" msgid="2776023299237058419">"連結:<xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"電郵地址:<xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"第 <xliff:g id="FIRST">%1$d</xliff:g> 至 <xliff:g id="LAST">%2$d</xliff:g> 頁 (共 <xliff:g id="TOTAL">%3$d</xliff:g> 頁)"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"圖片:<xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"在檔案中搜尋"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"找不到相符項目。"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"上一個"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"下一個"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"閂"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"編輯檔案"</string>
<string name="password_not_entered" msgid="8875370870743585303">"輸入密碼即可解鎖"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"無法開啟檔案。可能有權限問題?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF 文件頁面已損毀"</string>
<string name="needs_more_data" msgid="3520133467908240802">"沒有足夠資料處理 PDF 文件"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
index 57010b7..b2be3f4 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"檔案類型"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"這個檔案受密碼保護"</string>
<string name="label_password_first" msgid="4456258714097111908">"密碼"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"密碼不正確"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"縮放 <xliff:g id="FIRST">%1$d</xliff:g>%%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"前往第 <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> 頁"</string>
<string name="desc_page" msgid="5684226167093594168">"第 <xliff:g id="PAGE">%1$d</xliff:g> 頁"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"選取要加註的文字"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"輕觸要加註的區域"</string>
- <string name="action_cancel" msgid="5494417739210197522">"取消"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"無法顯示 PDF (「<xliff:g id="TITLE">%1$s</xliff:g>」的格式無效)"</string>
<string name="error_on_page" msgid="1592475819957182385">"無法顯示第 <xliff:g id="PAGE">%1$d</xliff:g> 頁 (檔案錯誤)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"無法載入這個項目的註解模式。"</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"連結:位於 <xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g> 的網頁"</string>
<string name="desc_web_link" msgid="2776023299237058419">"連結:<xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"電子郵件地址:<xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"第 <xliff:g id="FIRST">%1$d</xliff:g> 到 <xliff:g id="LAST">%2$d</xliff:g> 頁,共 <xliff:g id="TOTAL">%3$d</xliff:g> 頁"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"圖片:<xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"在檔案中搜尋"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"找不到相符項目。"</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"上一個"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"下一個"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"關閉"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"編輯檔案"</string>
<string name="password_not_entered" msgid="8875370870743585303">"輸入密碼即可解鎖"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"無法開啟檔案。有可能是權限問題?"</string>
<string name="page_broken" msgid="2968770793669433462">"PDF 文件的頁面損毀"</string>
<string name="needs_more_data" msgid="3520133467908240802">"資料不足,無法處理 PDF 文件"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zu/strings.xml b/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
index 8d4f6b1..ba456e5 100644
--- a/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
@@ -17,7 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="desc_file_type" msgid="8918077960128045611">"Uhlobo lwefayela"</string>
<string name="title_dialog_password" msgid="2018068413709926925">"Leli fayela livikelwe"</string>
<string name="label_password_first" msgid="4456258714097111908">"Iphasiwedi"</string>
<string name="label_password_incorrect" msgid="8449142641187704667">"Iphasiwedi ayilungile"</string>
@@ -31,12 +30,8 @@
<string name="desc_zoom" msgid="7318480946145947242">"sondeza iphesenti elingu-<xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Iya ekhasini <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
<string name="desc_page" msgid="5684226167093594168">"ikhasi <xliff:g id="PAGE">%1$d</xliff:g>"</string>
- <string name="message_select_text_to_comment" msgid="5725327644007067522">"Khetha umbhalo ukuze ubeke amazwana akho"</string>
- <string name="message_tap_to_comment" msgid="7820801719181709999">"Thepha indawo ukuze ubeke amazwana kuyo"</string>
- <string name="action_cancel" msgid="5494417739210197522">"Khansela"</string>
<string name="error_file_format_pdf" msgid="7567006188638831878">"Ayikwazi ukubonisa i-PDF (i-<xliff:g id="TITLE">%1$s</xliff:g> ingeyefomethi engavumelekile)"</string>
<string name="error_on_page" msgid="1592475819957182385">"Ayikwazi ukubonisa ikhasi elingu-<xliff:g id="PAGE">%1$d</xliff:g> (iphutha lefayela)"</string>
- <string name="annotation_mode_failed_to_open" msgid="1659648756255912463">"Ayikwazi ukulayisha imodi yesichasiselo yale nto."</string>
<string name="desc_web_link_shortened_to_domain" msgid="3323639528531061592">"Ilinki: ikhasi lewebhu ku-<xliff:g id="DESTINATION_DOMAIN">%1$s</xliff:g>"</string>
<string name="desc_web_link" msgid="2776023299237058419">"Ilinki: <xliff:g id="DESTINATION">%1$s</xliff:g>"</string>
<string name="desc_email_link" msgid="7027325672358507448">"I-imeyili: <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string>
@@ -47,7 +42,9 @@
<string name="desc_page_range" msgid="5286496438609641577">"amakhasi <xliff:g id="FIRST">%1$d</xliff:g> ukuya ku-<xliff:g id="LAST">%2$d</xliff:g> kwangu-<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Umfanekiso: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Thola kufayela"</string>
- <string name="message_no_matches_found" msgid="6965828658999779258">"Akukho okufanayo okutholiwe."</string>
+ <string name="previous_button_description" msgid="1169511027880317546">"Okwangaphambilini"</string>
+ <string name="next_button_description" msgid="4702699322249103693">"Okulandelayo"</string>
+ <string name="close_button_description" msgid="7379823906921067675">"Vala"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="action_edit" msgid="5882082700509010966">"Hlela ifayela"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Faka iphasiwedi ukuvula"</string>
@@ -56,4 +53,6 @@
<string name="file_error" msgid="4003885928556884091">"Yehlulekile ukuvula ifayela. Inkinga yemvume engaba khona?"</string>
<string name="page_broken" msgid="2968770793669433462">"Ikhasi eliphuliwe ledokhumenti ye-PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Idatha enganele yokucubungula idokhumenti ye-PDF"</string>
+ <!-- no translation found for error_cannot_open_pdf (2361919778558145071) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/colors.xml b/pdf/pdf-viewer/src/main/res/values/colors.xml
index a860b33..8730c12 100644
--- a/pdf/pdf-viewer/src/main/res/values/colors.xml
+++ b/pdf/pdf-viewer/src/main/res/values/colors.xml
@@ -15,21 +15,10 @@
-->
<resources>
+ <color name="pdf_viewer_color_primary">@color/m3_sys_color_light_primary</color>
+ <color name="pdf_viewer_color_on_surface">@color/m3_sys_color_light_on_surface</color>
+ <color name="pdf_viewer_color_error">@color/m3_sys_color_light_error</color>
+ <!-- m3_sys_color_primary_fixed_dim doesn't have a day/night version, therefore defined only once -->
+ <color name="pdf_viewer_selection_handles">@color/m3_sys_color_primary_fixed_dim</color>
+</resources>
- <color name="google_blue">#ff1a73e8</color>
- <color name="google_white">#ffffffff</color>
- <color name="google_grey">#ff3c4043</color>
- <color name="text_default">#666</color>
- <color name="text_error">#da4336</color>
- <color name="selection_handles">#00aadd</color>
- <color name="search_background">#F3EDE8</color>
- <color name="search_textbox">#FEF8F3</color>
- <color name="search_texthint">#494643</color>
- <color name="search_textColor">#1D1B19</color>
- <color name="search_count">#494643</color>
- <color name="search_prev_button">#494643</color>
- <color name="search_next_button">#494643</color>
- <color name="search_close_button">#494643</color>
-
-
-</resources>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/values/dimensions.xml b/pdf/pdf-viewer/src/main/res/values/dimensions.xml
index 67138e3..03bf958 100644
--- a/pdf/pdf-viewer/src/main/res/values/dimensions.xml
+++ b/pdf/pdf-viewer/src/main/res/values/dimensions.xml
@@ -16,14 +16,9 @@
<resources>
<dimen name="viewer_doc_additional_top_offset">12dp</dimen>
- <dimen name="viewer_fastscroll_edge_offset">15dp</dimen>
- <dimen name="viewer_fastscroll_rounded_corner_radius">2dp</dimen>
+ <dimen name="viewer_fastscroll_edge_offset">24dp</dimen>
<dimen name="viewer_doc_padding_y">4dp</dimen>
<dimen name="viewer_doc_padding_x">0dp</dimen>
- <dimen name="mtrl_min_touch_target_size">48dp</dimen>
-
- <dimen name="viewer_progress_bar_height">4dp</dimen>
- <dimen name="viewer_frame_error_height">80dp</dimen>
<!--Edit button FAB -->
<dimen name="viewer_edit_fab_margin_bottom">16dp</dimen>
diff --git a/pdf/pdf-viewer/src/main/res/values/strings.xml b/pdf/pdf-viewer/src/main/res/values/strings.xml
index 79cf9a2..5a8f507 100644
--- a/pdf/pdf-viewer/src/main/res/values/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values/strings.xml
@@ -15,10 +15,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Content description for an icon representing the file's type (e.g. image or pdf). If
- we know the exact file type, it will be appended to this description, and result in e.g.
- "file type: Image" [CHAR LIMIT=25] -->
- <string name="desc_file_type">File type</string>
<!-- Title of the password dialog [CHAR LIMIT=40] -->
<string name="title_dialog_password">This file is protected</string>
@@ -61,16 +57,6 @@
<!-- Content description for a page of a paginated document, eg "page 3". [CHAR LIMIT=50] -->
<string name="desc_page">page <xliff:g example="3" id="page">%1$d</xliff:g></string>
- <!-- Message for instructing the user to select text to create an inline comment anchor. [CHAR LIMIT=45] -->
- <string name="message_select_text_to_comment">Select text to place your comment</string>
-
- <!-- Message for instructing the user to tap to create a positional comment anchor. [CHAR LIMIT=40] -->
- <string name="message_tap_to_comment">Tap an area to comment on</string>
-
- <!-- Text for an action to cancel an operation. [CHAR LIMIT=20] -->
- <string name="action_cancel">Cancel</string>
-
-
<!-- Error message when file format isn't valid PDF. [CHAR LIMIT=60] -->
<string name="error_file_format_pdf">Cannot display PDF ("<xliff:g example="Treasure Island" id="title">%1$s</xliff:g>" is of invalid format)</string>
@@ -78,10 +64,6 @@
<string name="error_on_page">Cannot display page <xliff:g example="3" id="page">%1$d</xliff:g>
(file error)</string>
- <!-- Contents of a snackbar message that is shown when launching annotation mode fails. The user
- can try again but it is unlikely that will solve the problem. [CHAR LIMIT=100] -->
- <string name="annotation_mode_failed_to_open">Can\'t load annotation mode for this item.</string>
-
<!-- Content description for a web link within a document that has been shortened to only include the domain.
The entire URL is too long to read aloud, but the user should know that it is a link and the domain of the webpage that it links to. [CHAR LIMIT=40] -->
<string name="desc_web_link_shortened_to_domain">Link: webpage at <xliff:g example="www.example.com" id="destination_domain">%1$s</xliff:g></string>
@@ -126,9 +108,6 @@
<!-- Content description for close button in find in file menu -->
<string name="close_button_description">Close</string>
- <!-- Message for no matches found when searching for query text inside the file. [CHAR LIMIT=40] -->
- <string name="message_no_matches_found">No matches found.</string>
-
<!-- Message to show which match is selected out of how many matches when searching for query text inside the file.
e.g. "3 of 12" meaning "match 3 is selected, out of 12 matches" [CHAR LIMIT=10] -->
<string name="message_match_status"><xliff:g id="position" example="3">%1$d</xliff:g> / <xliff:g id="total" example="12">%2$d</xliff:g></string>
@@ -156,4 +135,7 @@
<!-- Exception message when the PDF document was insufficient file contents -->
<string name="needs_more_data">Insufficient data for processing the PDF document</string>
+
+ <!-- Error Message on PDF File Load Error -->
+ <string name="error_cannot_open_pdf">Can\'t open PDF file</string>
</resources>
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginationModelTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginationModelTest.java
index eecca89..a568c6f 100644
--- a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginationModelTest.java
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/PaginationModelTest.java
@@ -178,25 +178,25 @@
mPaginationModel.addPage(1, mdDimensions);
mPaginationModel.addPage(2, lgDimensions);
// Setting viewArea large enough to accommodate the entire model.
- mPaginationModel.setViewArea(new Rect(0, 0, 800, 800));
+ Rect viewArea = new Rect(0, 0, 800, 800);
Rect expectedSmLocation = new Rect(300, 0, 500, 100);
- assertThat(mPaginationModel.getPageLocation(0)).isEqualTo(expectedSmLocation);
+ assertThat(mPaginationModel.getPageLocation(0, viewArea)).isEqualTo(expectedSmLocation);
Rect expectedMdLocation =
new Rect(200, 100 + getSpacingAbovePage(1), 600, 300 + getSpacingAbovePage(1));
- assertThat(mPaginationModel.getPageLocation(1)).isEqualTo(expectedMdLocation);
+ assertThat(mPaginationModel.getPageLocation(1, viewArea)).isEqualTo(expectedMdLocation);
Rect expectedLgLocation =
new Rect(0, 300 + getSpacingAbovePage(2), 800, 700 + getSpacingAbovePage(2));
- Assert.assertEquals(expectedLgLocation, mPaginationModel.getPageLocation(2));
- assertThat(mPaginationModel.getPageLocation(2)).isEqualTo(expectedLgLocation);
+ assertThat(mPaginationModel.getPageLocation(2, viewArea)).isEqualTo(expectedLgLocation);
}
/**
- * {@link PaginationModel#getPageLocation(int)} should try to fit as much of each page into the
- * viewable area as possible. Dimensions do not change vertically but pages that are smaller
- * than {@link PaginationModel#getWidth()} can be moved horizontally to make this happen.
+ * {@link PaginationModel#getPageLocation(int, Rect)} should try to fit as much of each page
+ * into the viewable area as possible. Dimensions do not change vertically but pages that are
+ * smaller than {@link PaginationModel#getWidth()} can be moved horizontally to make this
+ * happen.
*
* <p>Page 1's width is smaller than {@link PaginationModel#getWidth()} so it will be placed in
* the middle when the visible area covers the whole model {@see #testGetPageLocation}. When the
@@ -216,11 +216,11 @@
mPaginationModel.addPage(2, lgDimensions);
// Setting viewArea to a 300x200 section in the bottom-left corner of the model.
- mPaginationModel.setViewArea(new Rect(0, 500, 200, 800));
+ Rect viewArea = new Rect(0, 500, 200, 800);
Rect expectedMdLocation =
new Rect(0, 100 + getSpacingAbovePage(1), 400, 300 + getSpacingAbovePage(1));
- assertThat(mPaginationModel.getPageLocation(1)).isEqualTo(expectedMdLocation);
+ assertThat(mPaginationModel.getPageLocation(1, viewArea)).isEqualTo(expectedMdLocation);
}
/**
diff --git a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/ZoomScrollValueObserverTest.java b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/ZoomScrollValueObserverTest.java
index b19e190..93e2b09 100644
--- a/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/ZoomScrollValueObserverTest.java
+++ b/pdf/pdf-viewer/src/test/java/androidx/pdf/viewer/ZoomScrollValueObserverTest.java
@@ -80,7 +80,7 @@
mNewPosition = new ZoomView.ZoomScroll(1.0f, 0, 0, false);
when(mMockPaginatedView.getPageRangeHandler()).thenReturn(mPageRangeHandler);
- when(mMockPaginatedView.getPaginationModel()).thenReturn(mMockPaginationModel);
+ when(mMockPaginatedView.getModel()).thenReturn(mMockPaginationModel);
when(mMockPaginationModel.isInitialized()).thenReturn(true);
when(mMockZoomView.getHeight()).thenReturn(100);
when(mPageRangeHandler.computeVisibleRange(0, 1.0f, 100, false)).thenReturn(PAGE_RANGE);
@@ -103,7 +103,7 @@
zoomScrollValueObserver.onChange(OLD_POSITION, mNewPosition);
verify(mMockZoomView).setStableZoom(1.0f);
- verify(mMockPaginationModel).setViewArea(RECT);
+ verify(mMockPaginatedView).setViewArea(RECT);
verify(mMockPaginatedView).refreshPageRangeInVisibleArea(mNewPosition, 100);
verify(mMockPaginatedView).handleGonePages(false);
verify(mMockPaginatedView).loadInvisibleNearPageRange(1.0f);
diff --git a/playground-common/playground-plugin/build.gradle b/playground-common/playground-plugin/build.gradle
index c5b8bb8..8081016 100644
--- a/playground-common/playground-plugin/build.gradle
+++ b/playground-common/playground-plugin/build.gradle
@@ -21,7 +21,7 @@
dependencies {
implementation(project(":shared"))
- implementation("com.gradle:develocity-gradle-plugin:3.17.2")
+ implementation("com.gradle:develocity-gradle-plugin:3.18")
implementation("com.gradle:common-custom-user-data-gradle-plugin:2.0.1")
implementation("supportBuildSrc:private")
implementation("supportBuildSrc:public")
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index e3086fc..b7c215d 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -26,5 +26,5 @@
# Disable docs
androidx.enableDocumentation=false
androidx.playground.snapshotBuildId=11349412
-androidx.playground.metalavaBuildId=12216051
-androidx.studio.type=playground
\ No newline at end of file
+androidx.playground.metalavaBuildId=12253410
+androidx.studio.type=playground
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/VersionCompatUtil.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/VersionCompatUtil.kt
index e685b4c..ef1a4b4 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/VersionCompatUtil.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/VersionCompatUtil.kt
@@ -32,25 +32,9 @@
SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= minVersion
}
- fun isRWithMinExtServicesVersion(minVersion: Int): Boolean {
- return Build.VERSION.SDK_INT == 30 &&
- SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= minVersion
- }
-
// Helper method to determine if version is testable, for APIs that are S+ only
fun isTestableVersion(minAdServicesVersion: Int, minExtServicesVersion: Int): Boolean {
return isTPlusWithMinAdServicesVersion(minAdServicesVersion) ||
isSWithMinExtServicesVersion(minExtServicesVersion)
}
-
- // Helper method to determine if version is testable, for APIs that are available on R
- fun isTestableVersion(
- minAdServicesVersion: Int,
- minExtServicesVersionS: Int,
- minExtServicesVersionR: Int
- ): Boolean {
- return isTPlusWithMinAdServicesVersion(minAdServicesVersion) ||
- isSWithMinExtServicesVersion(minExtServicesVersionS) ||
- isRWithMinExtServicesVersion(minExtServicesVersionR)
- }
}
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adid/AdIdManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adid/AdIdManagerFuturesTest.kt
index dcf44e7d..3b2c545 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adid/AdIdManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adid/AdIdManagerFuturesTest.kt
@@ -17,7 +17,6 @@
package androidx.privacysandbox.ads.adservices.java.adid
import android.adservices.adid.AdIdManager
-import android.adservices.common.AdServicesOutcomeReceiver
import android.content.Context
import android.os.Looper
import android.os.OutcomeReceiver
@@ -54,14 +53,12 @@
private var mSession: StaticMockitoSession? = null
private val mValidAdExtServicesSdkExtVersionS =
VersionCompatUtil.isSWithMinExtServicesVersion(9)
- private val mValidAdExtServicesSdkExtVersionR =
- VersionCompatUtil.isRWithMinExtServicesVersion(11)
@Before
fun setUp() {
mContext = spy(ApplicationProvider.getApplicationContext<Context>())
- if (mValidAdExtServicesSdkExtVersionS || mValidAdExtServicesSdkExtVersionR) {
+ if (mValidAdExtServicesSdkExtVersionS) {
// setup a mockitoSession to return the mocked manager
// when the static method .get() is called
mSession =
@@ -78,11 +75,10 @@
@SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
fun testAdIdOlderVersions() {
Assume.assumeFalse(
- "maxSdkVersion = API 33 ext 3 or API 31/32 ext 8 or API 30 ext 10",
+ "maxSdkVersion = API 33 ext 3 or API 31/32 ext 8",
VersionCompatUtil.isTestableVersion(
/* minAdServicesVersion=*/ 4,
/* minExtServicesVersionS=*/ 9,
- /* minExtServicesVersionR=*/ 11
)
)
Truth.assertThat(AdIdManagerFutures.from(mContext)).isEqualTo(null)
@@ -91,24 +87,15 @@
@Test
fun testAdIdAsync() {
Assume.assumeTrue(
- "minSdkVersion = API 33 ext 4 or API 31/32 ext 9 or API 30 ext 11",
+ "minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
VersionCompatUtil.isTestableVersion(
/* minAdServicesVersion= */ 4,
/* minExtServicesVersionS=*/ 9,
- /* minExtServicesVersionR=*/ 11
)
)
- val adIdManager =
- mockAdIdManager(
- mContext,
- mValidAdExtServicesSdkExtVersionS || mValidAdExtServicesSdkExtVersionR
- )
-
- when (mValidAdExtServicesSdkExtVersionR) {
- true -> setupResponseR(adIdManager)
- false -> setupResponseSPlus(adIdManager)
- }
+ val adIdManager = mockAdIdManager(mContext, mValidAdExtServicesSdkExtVersionS)
+ setupResponseSPlus(adIdManager)
val managerCompat = AdIdManagerFutures.from(mContext)
@@ -117,11 +104,7 @@
// Verify that the result of the compat call is correct.
verifyResponse(result.get())
-
- when (mValidAdExtServicesSdkExtVersionR) {
- true -> verifyOnR(adIdManager)
- false -> verifyOnSPlus(adIdManager)
- }
+ verifyOnSPlus(adIdManager)
}
@SdkSuppress(minSdkVersion = 30)
@@ -157,37 +140,6 @@
)
}
- private fun setupResponseR(adIdManager: AdIdManager) {
- // Set up the response that AdIdManager will return when the compat code calls it.
- val adId = android.adservices.adid.AdId("1234", false)
- val answer = { args: InvocationOnMock ->
- assertNotEquals(Looper.getMainLooper(), Looper.myLooper())
- val receiver =
- args.getArgument<
- AdServicesOutcomeReceiver<android.adservices.adid.AdId, Exception>
- >(
- 1
- )
- receiver.onResult(adId)
- null
- }
- Mockito.doAnswer(answer)
- .`when`(adIdManager)
- .getAdId(
- any<Executor>(),
- any<AdServicesOutcomeReceiver<android.adservices.adid.AdId, Exception>>()
- )
- }
-
- private fun verifyOnR(adIdManager: AdIdManager) {
- // Verify that the compat code was invoked correctly.
- Mockito.verify(adIdManager)
- .getAdId(
- any<Executor>(),
- any<AdServicesOutcomeReceiver<android.adservices.adid.AdId, Exception>>()
- )
- }
-
private fun verifyOnSPlus(adIdManager: AdIdManager) {
// Verify that the compat code was invoked correctly.
Mockito.verify(adIdManager)
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java
index 8eea312..2df8a7a 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java
@@ -115,17 +115,6 @@
}
}
- public void enableBackCompatOnR() {
- runShellCommand("device_config put adservices adservice_enabled true");
- runShellCommand("device_config put adservices enable_back_compat true");
- }
-
-
- public void disableBackCompatOnR() {
- runShellCommand("device_config put adservices adservice_enabled false");
- runShellCommand("device_config put adservices enable_back_compat false");
- }
-
public void enableBackCompatOnS() {
runShellCommand("device_config put adservices enable_back_compat true");
runShellCommand("device_config put adservices consent_source_of_truth 3");
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/adid/AdIdManagerTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/adid/AdIdManagerTest.java
index dd54c93..dc17deb 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/adid/AdIdManagerTest.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/adid/AdIdManagerTest.java
@@ -80,8 +80,7 @@
Assume.assumeTrue(
VersionCompatUtil.INSTANCE.isTestableVersion(
/* minAdServicesVersion= */ 4,
- /* minExtServicesVersionS= */ 9,
- /* minExtServicesVersionR= */ 11));
+ /* minExtServicesVersionS= */ 9));
AdIdManagerFutures adIdManager =
AdIdManagerFutures.from(ApplicationProvider.getApplicationContext());
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
index fc16065..7f45668 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
@@ -96,8 +96,6 @@
MeasurementManagerFutures.from(ApplicationProvider.getApplicationContext());
if (VersionCompatUtil.INSTANCE.isSWithMinExtServicesVersion(9)) {
mTestUtil.enableBackCompatOnS();
- } else if (VersionCompatUtil.INSTANCE.isRWithMinExtServicesVersion(11)) {
- mTestUtil.enableBackCompatOnR();
}
// Put in a short sleep to make sure the updated config propagates
@@ -115,8 +113,6 @@
mTestUtil.overrideDisableMeasurementEnrollmentCheck("0");
if (VersionCompatUtil.INSTANCE.isSWithMinExtServicesVersion(9)) {
mTestUtil.disableBackCompatOnS();
- } else if (VersionCompatUtil.INSTANCE.isRWithMinExtServicesVersion(11)) {
- mTestUtil.disableBackCompatOnR();
}
// Cool-off rate limiter
@@ -129,8 +125,7 @@
Assume.assumeTrue(
VersionCompatUtil.INSTANCE.isTestableVersion(
/* minAdServicesVersion= */ 5,
- /* minExtServicesVersionS= */ 9,
- /* minExtServicesVersionR= */ 11));
+ /* minExtServicesVersionS= */ 9));
assertThat(
mMeasurementManager
@@ -146,8 +141,7 @@
Assume.assumeTrue(
VersionCompatUtil.INSTANCE.isTestableVersion(
/* minAdServicesVersion= */ 5,
- /* minExtServicesVersionS= */ 9,
- /* minExtServicesVersionR= */ 11));
+ /* minExtServicesVersionS= */ 9));
SourceRegistrationRequest request =
new SourceRegistrationRequest.Builder(
@@ -162,8 +156,7 @@
Assume.assumeTrue(
VersionCompatUtil.INSTANCE.isTestableVersion(
/* minAdServicesVersion= */ 5,
- /* minExtServicesVersionS= */ 9,
- /* minExtServicesVersionR= */ 11));
+ /* minExtServicesVersionS= */ 9));
assertThat(mMeasurementManager.registerTriggerAsync(TRIGGER_REGISTRATION_URI).get())
.isNotNull();
@@ -175,8 +168,7 @@
Assume.assumeTrue(
VersionCompatUtil.INSTANCE.isTestableVersion(
/* minAdServicesVersion= */ 5,
- /* minExtServicesVersionS= */ 9,
- /* minExtServicesVersionR= */ 11));
+ /* minExtServicesVersionS= */ 9));
WebSourceParams webSourceParams = new WebSourceParams(SOURCE_REGISTRATION_URI, false);
@@ -199,8 +191,7 @@
Assume.assumeTrue(
VersionCompatUtil.INSTANCE.isTestableVersion(
/* minAdServicesVersion= */ 5,
- /* minExtServicesVersionS= */ 9,
- /* minExtServicesVersionR= */ 11));
+ /* minExtServicesVersionS= */ 9));
WebTriggerParams webTriggerParams = new WebTriggerParams(TRIGGER_REGISTRATION_URI, false);
WebTriggerRegistrationRequest webTriggerRegistrationRequest =
@@ -236,8 +227,7 @@
Assume.assumeTrue(
VersionCompatUtil.INSTANCE.isTestableVersion(
/* minAdServicesVersion= */ 5,
- /* minExtServicesVersionS= */ 9,
- /* minExtServicesVersionR= */ 11));
+ /* minExtServicesVersionS= */ 9));
DeletionRequest deletionRequest =
new DeletionRequest.Builder(
@@ -258,8 +248,7 @@
Assume.assumeTrue(
VersionCompatUtil.INSTANCE.isTestableVersion(
/* minAdServicesVersion= */ 5,
- /* minExtServicesVersionS= */ 9,
- /* minExtServicesVersionR= */ 11));
+ /* minExtServicesVersionS= */ 9));
DeletionRequest deletionRequest =
new DeletionRequest.Builder(
@@ -283,8 +272,7 @@
Assume.assumeTrue(
VersionCompatUtil.INSTANCE.isTestableVersion(
/* minAdServicesVersion= */ 5,
- /* minExtServicesVersionS= */ 9,
- /* minExtServicesVersionR= */ 11));
+ /* minExtServicesVersionS= */ 9));
int result = mMeasurementManager.getMeasurementApiStatusAsync().get();
assertThat(result).isEqualTo(1);
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
index 363191c..eeae81f 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
@@ -16,7 +16,6 @@
package androidx.privacysandbox.ads.adservices.java.measurement
-import android.adservices.common.AdServicesOutcomeReceiver
import android.adservices.measurement.MeasurementManager
import android.content.Context
import android.net.Uri
@@ -75,16 +74,12 @@
private var mSession: StaticMockitoSession? = null
private val mValidAdExtServicesSdkExtVersionS =
VersionCompatUtil.isSWithMinExtServicesVersion(9)
- private val mValidAdExtServicesSdkExtVersionR =
- VersionCompatUtil.isRWithMinExtServicesVersion(11)
- private val mValidExtServicesVersion =
- mValidAdExtServicesSdkExtVersionS || mValidAdExtServicesSdkExtVersionR
@Before
fun setUp() {
mContext = spy(ApplicationProvider.getApplicationContext<Context>())
- if (mValidExtServicesVersion) {
+ if (mValidAdExtServicesSdkExtVersionS) {
// setup a mockitoSession to return the mocked manager
// when the static method .get() is called
mSession =
@@ -104,11 +99,10 @@
@SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
fun testMeasurementOlderVersions() {
Assume.assumeFalse(
- "maxSdkVersion = API 33 ext 4 or API 31/32 ext 8 or API 30 ext 10",
+ "maxSdkVersion = API 33 ext 4 or API 31/32 ext 8",
VersionCompatUtil.isTestableVersion(
/* minAdServicesVersion=*/ 5,
/* minExtServicesVersionS=*/ 9,
- /* minExtServicesVersionR=*/ 11
)
)
assertThat(from(mContext)).isEqualTo(null)
@@ -556,410 +550,6 @@
)
}
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testDeleteRegistrationsAsyncOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val mMeasurementManager =
- mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val managerCompat = from(mContext)
-
- // Set up the request.
- val answer = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(2)
- receiver.onResult(Object())
- assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
- null
- }
- doAnswer(answer)
- .`when`(mMeasurementManager)
- .deleteRegistrations(
- any<android.adservices.measurement.DeletionRequest>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, java.lang.Exception>>()
- )
-
- // Actually invoke the compat code.
- val request =
- DeletionRequest(
- DeletionRequest.DELETION_MODE_ALL,
- DeletionRequest.MATCH_BEHAVIOR_DELETE,
- Instant.now(),
- Instant.now(),
- listOf(uri1),
- listOf(uri1)
- )
-
- managerCompat!!.deleteRegistrationsAsync(request).get()
-
- // Verify that the compat code was invoked correctly.
- val captor =
- ArgumentCaptor.forClass(android.adservices.measurement.DeletionRequest::class.java)
- verify(mMeasurementManager)
- .deleteRegistrations(
- captor.capture(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, java.lang.Exception>>()
- )
-
- // Verify that the request that the compat code makes to the platform is correct.
- verifyDeletionRequest(captor.value)
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterSourceAsyncOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val mMeasurementManager =
- mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val inputEvent = mock(InputEvent::class.java)
- val managerCompat = from(mContext)
-
- val answer = { args: InvocationOnMock ->
- assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(3)
- receiver.onResult(Object())
- null
- }
- doAnswer(answer)
- .`when`(mMeasurementManager)
- .registerSource(
- any<Uri>(),
- any<InputEvent>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Actually invoke the compat code.
- managerCompat!!.registerSourceAsync(uri1, inputEvent).get()
-
- // Verify that the compat code was invoked correctly.
- val captor1 = ArgumentCaptor.forClass(Uri::class.java)
- val captor2 = ArgumentCaptor.forClass(InputEvent::class.java)
- verify(mMeasurementManager)
- .registerSource(
- captor1.capture(),
- captor2.capture(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Verify that the request that the compat code makes to the platform is correct.
- assertThat(captor1.value == uri1)
- assertThat(captor2.value == inputEvent)
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterTriggerAsyncOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val mMeasurementManager =
- mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val managerCompat = from(mContext)
-
- val answer = { args: InvocationOnMock ->
- assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(2)
- receiver.onResult(Object())
- null
- }
- doAnswer(answer)
- .`when`(mMeasurementManager)
- .registerTrigger(
- any<Uri>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Actually invoke the compat code.
- managerCompat!!.registerTriggerAsync(uri1).get()
-
- // Verify that the compat code was invoked correctly.
- val captor1 = ArgumentCaptor.forClass(Uri::class.java)
- verify(mMeasurementManager)
- .registerTrigger(
- captor1.capture(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Verify that the request that the compat code makes to the platform is correct.
- assertThat(captor1.value == uri1)
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterWebSourceAsyncOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val mMeasurementManager =
- mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val managerCompat = from(mContext)
-
- val answer = { args: InvocationOnMock ->
- assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(2)
- receiver.onResult(Object())
- null
- }
- doAnswer(answer)
- .`when`(mMeasurementManager)
- .registerWebSource(
- any<android.adservices.measurement.WebSourceRegistrationRequest>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- val request =
- WebSourceRegistrationRequest.Builder(listOf(WebSourceParams(uri2, false)), uri1)
- .setAppDestination(appDestination)
- .build()
-
- // Actually invoke the compat code.
- managerCompat!!.registerWebSourceAsync(request).get()
-
- // Verify that the compat code was invoked correctly.
- val captor1 =
- ArgumentCaptor.forClass(
- android.adservices.measurement.WebSourceRegistrationRequest::class.java
- )
- verify(mMeasurementManager)
- .registerWebSource(
- captor1.capture(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Verify that the request that the compat code makes to the platform is correct.
- val actualRequest = captor1.value
- assertThat(actualRequest.topOriginUri == uri1)
- assertThat(actualRequest.sourceParams.size == 1)
- assertThat(actualRequest.appDestination == appDestination)
- assertThat(actualRequest.sourceParams[0].registrationUri == uri2)
- assertThat(!actualRequest.sourceParams[0].isDebugKeyAllowed)
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterWebTriggerAsyncOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val mMeasurementManager =
- mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val managerCompat = from(mContext)
-
- val answer = { args: InvocationOnMock ->
- assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(2)
- receiver.onResult(Object())
- null
- }
- doAnswer(answer)
- .`when`(mMeasurementManager)
- .registerWebTrigger(
- any<android.adservices.measurement.WebTriggerRegistrationRequest>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- val request = WebTriggerRegistrationRequest(listOf(WebTriggerParams(uri1, false)), uri2)
-
- // Actually invoke the compat code.
- managerCompat!!.registerWebTriggerAsync(request).get()
-
- // Verify that the compat code was invoked correctly.
- val captor1 =
- ArgumentCaptor.forClass(
- android.adservices.measurement.WebTriggerRegistrationRequest::class.java
- )
- verify(mMeasurementManager)
- .registerWebTrigger(
- captor1.capture(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Verify that the request that the compat code makes to the platform is correct.
- val actualRequest = captor1.value
- assertThat(actualRequest.destination == uri2)
- assertThat(actualRequest.triggerParams.size == 1)
- assertThat(actualRequest.triggerParams[0].registrationUri == uri1)
- assertThat(!actualRequest.triggerParams[0].isDebugKeyAllowed)
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testMeasurementApiStatusAsyncOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val mMeasurementManager =
- mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val managerCompat = from(mContext)
-
- val state = MeasurementManager.MEASUREMENT_API_STATE_DISABLED
- val answer = { args: InvocationOnMock ->
- assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Int, Exception>>(1)
- receiver.onResult(state)
- null
- }
- doAnswer(answer)
- .`when`(mMeasurementManager)
- .getMeasurementApiStatus(
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Int, Exception>>()
- )
-
- // Actually invoke the compat code.
- val result = managerCompat!!.getMeasurementApiStatusAsync()
- result.get()
-
- // Verify that the compat code was invoked correctly.
- verify(mMeasurementManager)
- .getMeasurementApiStatus(
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Int, Exception>>()
- )
-
- // Verify that the result.
- assertThat(result.get() == state)
- }
-
- @ExperimentalFeatures.RegisterSourceOptIn
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterSourceAsync_allSuccessOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val mMeasurementManager =
- mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val inputEvent = mock(InputEvent::class.java)
- val managerCompat = from(mContext)
-
- val successCallback = { args: InvocationOnMock ->
- assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(3)
- receiver.onResult(Object())
- null
- }
- doAnswer(successCallback)
- .`when`(mMeasurementManager)
- .registerSource(
- any<Uri>(),
- any<InputEvent>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Actually invoke the compat code.
- val request =
- SourceRegistrationRequest.Builder(listOf(uri1, uri2)).setInputEvent(inputEvent).build()
- managerCompat!!.registerSourceAsync(request).get()
-
- // Verify that the compat code was invoked correctly.
- verify(mMeasurementManager)
- .registerSource(
- eq(uri1),
- eq(inputEvent),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
- verify(mMeasurementManager)
- .registerSource(
- eq(uri2),
- eq(inputEvent),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
- }
-
- @ExperimentalFeatures.RegisterSourceOptIn
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterSource_15thOf20Fails_atLeast15thExecutesOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val mMeasurementManager =
- mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val mockInputEvent = mock(InputEvent::class.java)
- val managerCompat = from(mContext)
-
- val successCallback = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(3)
- receiver.onResult(Object())
- null
- }
-
- val errorMessage = "some error occurred"
- val errorCallback = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(3)
- receiver.onError(IllegalArgumentException(errorMessage))
- null
- }
-
- val uris =
- (1..20)
- .map { i ->
- val uri = Uri.parse("www.uri$i.com")
- if (i == 15) {
- doAnswer(errorCallback)
- .`when`(mMeasurementManager)
- .registerSource(
- eq(uri),
- any<InputEvent>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
- } else {
- doAnswer(successCallback)
- .`when`(mMeasurementManager)
- .registerSource(
- eq(uri),
- any<InputEvent>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
- }
- uri
- }
- .toList()
-
- val request = SourceRegistrationRequest(uris, mockInputEvent)
-
- // Actually invoke the compat code.
- runBlocking {
- try {
- withContext(Dispatchers.Main) { managerCompat!!.registerSourceAsync(request).get() }
- fail("Expected failure.")
- } catch (e: ExecutionException) {
- assertTrue(e.cause!! is IllegalArgumentException)
- assertThat(e.cause!!.message).isEqualTo(errorMessage)
- }
- }
-
- // Verify that the compat code was invoked correctly.
- // registerSource gets called 1-20 times. We cannot predict the exact number because
- // uri15 would crash asynchronously. Other uris may succeed and those threads on default
- // dispatcher won't crash.
- verify(mMeasurementManager, atLeastOnce())
- .registerSource(
- any<Uri>(),
- eq(mockInputEvent),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
- verify(mMeasurementManager, atMost(20))
- .registerSource(
- any<Uri>(),
- eq(mockInputEvent),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
- }
-
@SdkSuppress(minSdkVersion = 30)
companion object {
diff --git a/privacysandbox/ads/ads-adservices/build.gradle b/privacysandbox/ads/ads-adservices/build.gradle
index e35df7a..5610d1a 100644
--- a/privacysandbox/ads/ads-adservices/build.gradle
+++ b/privacysandbox/ads/ads-adservices/build.gradle
@@ -51,10 +51,6 @@
}
android {
- buildTypes.all {
- consumerProguardFiles "proguard-rules.pro"
- }
-
compileSdk = 34
compileSdkExtension = 12
namespace "androidx.privacysandbox.ads.adservices"
diff --git a/privacysandbox/ads/ads-adservices/proguard-rules.pro b/privacysandbox/ads/ads-adservices/proguard-rules.pro
deleted file mode 100644
index e092df1..0000000
--- a/privacysandbox/ads/ads-adservices/proguard-rules.pro
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) 2024 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.
-
-# A rule that will keep the internal ContinuationOutcomeReceiver class used to
-# work with AdServicesOutcomeReceiver on Android R
--keep class androidx.privacysandbox.ads.adservices.internal.ContinuationOutcomeReceiver {
- <methods>;
-}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerTest.kt
index b1b53a6..b937f1d 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerTest.kt
@@ -16,7 +16,6 @@
package androidx.privacysandbox.ads.adservices.adid
-import android.adservices.common.AdServicesOutcomeReceiver
import android.content.Context
import android.os.OutcomeReceiver
import android.os.ext.SdkExtensions
@@ -53,13 +52,12 @@
private var mSession: StaticMockitoSession? = null
private val mValidAdServicesSdkExtVersion = AdServicesInfo.adServicesVersion() >= 4
private val mValidAdExtServicesSdkExtVersionS = AdServicesInfo.extServicesVersionS() >= 9
- private val mValidAdExtServicesSdkExtVersionR = AdServicesInfo.extServicesVersionR() >= 11
@Before
fun setUp() {
mContext = spy(ApplicationProvider.getApplicationContext<Context>())
- if (mValidAdExtServicesSdkExtVersionS || mValidAdExtServicesSdkExtVersionR) {
+ if (mValidAdExtServicesSdkExtVersionS) {
// setup a mockitoSession to return the mocked manager
// when the static method .get() is called
mSession =
@@ -79,16 +77,12 @@
fun testAdIdOlderVersions() {
Assume.assumeTrue("maxSdkVersion = API 33 ext 3", !mValidAdServicesSdkExtVersion)
Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersionS)
- Assume.assumeTrue("maxSdkVersion = API 30 ext 10", !mValidAdExtServicesSdkExtVersionR)
assertThat(AdIdManager.obtain(mContext)).isNull()
}
@Test
fun testAdIdManagerNoClassDefFoundError() {
- Assume.assumeTrue(
- "minSdkVersion = API 31/32 ext 9 or API 30 ext 11",
- mValidAdExtServicesSdkExtVersionS || mValidAdExtServicesSdkExtVersionR
- )
+ Assume.assumeTrue("minSdkVersion = API 31/32 ext 9", mValidAdExtServicesSdkExtVersionS)
`when`(android.adservices.adid.AdIdManager.get(any())).thenThrow(NoClassDefFoundError())
assertThat(AdIdManager.obtain(mContext)).isNull()
@@ -96,19 +90,13 @@
@Test
fun testAdIdAsync() {
- val validExtServicesVersion =
- mValidAdExtServicesSdkExtVersionS || mValidAdExtServicesSdkExtVersionR
Assume.assumeTrue(
- "minSdkVersion = API 33 ext 4 or API 31/32 ext 9 or API 30 ext 11",
- mValidAdServicesSdkExtVersion || validExtServicesVersion
+ "minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+ mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersionS
)
- val adIdManager = mockAdIdManager(mContext, validExtServicesVersion)
-
- when (mValidAdExtServicesSdkExtVersionR) {
- true -> setupResponseR(adIdManager)
- false -> setupResponseSPlus(adIdManager)
- }
+ val adIdManager = mockAdIdManager(mContext, mValidAdExtServicesSdkExtVersionS)
+ setupResponseSPlus(adIdManager)
val managerCompat = AdIdManager.obtain(mContext)
@@ -116,10 +104,7 @@
val result = runBlocking { managerCompat!!.getAdId() }
// Verify that the compat code was invoked correctly.
- when (mValidAdExtServicesSdkExtVersionR) {
- true -> verifyOnR(adIdManager)
- false -> verifyOnSPlus(adIdManager)
- }
+ verifyOnSPlus(adIdManager)
// Verify that the result of the compat call is correct.
verifyResponse(result)
@@ -162,35 +147,6 @@
)
}
- private fun setupResponseR(adIdManager: android.adservices.adid.AdIdManager) {
- // Set up the response that AdIdManager will return when the compat code calls it.
- val adId = android.adservices.adid.AdId("1234", false)
- val answer = { args: InvocationOnMock ->
- val receiver =
- args.getArgument<
- AdServicesOutcomeReceiver<android.adservices.adid.AdId, Exception>
- >(
- 1
- )
- receiver.onResult(adId)
- null
- }
- doAnswer(answer)
- .`when`(adIdManager)
- .getAdId(
- any<Executor>(),
- any<AdServicesOutcomeReceiver<android.adservices.adid.AdId, Exception>>()
- )
- }
-
- private fun verifyOnR(adIdManager: android.adservices.adid.AdIdManager) {
- verify(adIdManager)
- .getAdId(
- any<Executor>(),
- any<AdServicesOutcomeReceiver<android.adservices.adid.AdId, Exception>>()
- )
- }
-
private fun verifyOnSPlus(adIdManager: android.adservices.adid.AdIdManager) {
verify(adIdManager)
.getAdId(
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
index 77ef543..dd323de 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
@@ -16,7 +16,6 @@
package androidx.privacysandbox.ads.adservices.measurement
-import android.adservices.common.AdServicesOutcomeReceiver
import android.adservices.measurement.MeasurementManager
import android.content.Context
import android.net.Uri
@@ -63,15 +62,12 @@
private var mSession: StaticMockitoSession? = null
private val mValidAdServicesSdkExtVersion = AdServicesInfo.adServicesVersion() >= 5
private val mValidAdExtServicesSdkExtVersionS = AdServicesInfo.extServicesVersionS() >= 9
- private val mValidAdExtServicesSdkExtVersionR = AdServicesInfo.extServicesVersionR() >= 11
- private val mValidExtServicesVersion =
- mValidAdExtServicesSdkExtVersionS || mValidAdExtServicesSdkExtVersionR
@Before
fun setUp() {
mContext = spy(ApplicationProvider.getApplicationContext<Context>())
- if (mValidExtServicesVersion) {
+ if (mValidAdExtServicesSdkExtVersionS) {
// setup a mockitoSession to return the mocked manager
// when the static method .get() is called
mSession =
@@ -92,16 +88,12 @@
fun testMeasurementOlderVersions() {
Assume.assumeTrue("maxSdkVersion = API 33 ext 4", !mValidAdServicesSdkExtVersion)
Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersionS)
- Assume.assumeTrue("maxSdkVersion = API 30 ext 10", !mValidAdExtServicesSdkExtVersionR)
assertThat(obtain(mContext)).isNull()
}
@Test
fun testMeasurementManagerNoClassDefFoundError() {
- Assume.assumeTrue(
- "minSdkVersion = API 31/32 ext 9 or API 30 ext 11",
- mValidExtServicesVersion
- )
+ Assume.assumeTrue("minSdkVersion = API 31/32 ext 9", mValidAdExtServicesSdkExtVersionS)
`when`(MeasurementManager.get(any())).thenThrow(NoClassDefFoundError())
assertThat(obtain(mContext)).isNull()
@@ -503,379 +495,6 @@
)
}
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testDeleteRegistrationsOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val managerCompat = obtain(mContext)
-
- // Set up the request.
- val answer = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(2)
- receiver.onResult(Object())
- null
- }
- doAnswer(answer)
- .`when`(measurementManager)
- .deleteRegistrations(
- any<android.adservices.measurement.DeletionRequest>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, java.lang.Exception>>()
- )
-
- // Actually invoke the compat code.
- runBlocking {
- val request =
- DeletionRequest(
- DeletionRequest.DELETION_MODE_ALL,
- DeletionRequest.MATCH_BEHAVIOR_DELETE,
- Instant.now(),
- Instant.now(),
- listOf(uri1),
- listOf(uri1)
- )
-
- managerCompat!!.deleteRegistrations(request)
- }
-
- // Verify that the compat code was invoked correctly.
- val captor =
- ArgumentCaptor.forClass(android.adservices.measurement.DeletionRequest::class.java)
- verify(measurementManager)
- .deleteRegistrations(
- captor.capture(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, java.lang.Exception>>()
- )
-
- // Verify that the request that the compat code makes to the platform is correct.
- verifyDeletionRequest(captor.value)
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterSourceOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val inputEvent = mock(InputEvent::class.java)
- val managerCompat = obtain(mContext)
-
- val answer = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(3)
- receiver.onResult(Object())
- null
- }
- doAnswer(answer)
- .`when`(measurementManager)
- .registerSource(
- any<Uri>(),
- any<InputEvent>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Actually invoke the compat code.
- runBlocking { managerCompat!!.registerSource(uri1, inputEvent) }
-
- // Verify that the compat code was invoked correctly.
- val captor1 = ArgumentCaptor.forClass(Uri::class.java)
- val captor2 = ArgumentCaptor.forClass(InputEvent::class.java)
- verify(measurementManager)
- .registerSource(
- captor1.capture(),
- captor2.capture(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Verify that the request that the compat code makes to the platform is correct.
- assertThat(captor1.value == uri1)
- assertThat(captor2.value == inputEvent)
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterTriggerOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val managerCompat = obtain(mContext)
- val answer = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(2)
- receiver.onResult(Object())
- null
- }
- doAnswer(answer)
- .`when`(measurementManager)
- .registerTrigger(
- any<Uri>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Actually invoke the compat code.
- runBlocking { managerCompat!!.registerTrigger(uri1) }
-
- // Verify that the compat code was invoked correctly.
- val captor1 = ArgumentCaptor.forClass(Uri::class.java)
- verify(measurementManager)
- .registerTrigger(
- captor1.capture(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Verify that the request that the compat code makes to the platform is correct.
- assertThat(captor1.value).isEqualTo(uri1)
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterWebSourceOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val managerCompat = obtain(mContext)
- val answer = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(2)
- receiver.onResult(Object())
- null
- }
- doAnswer(answer)
- .`when`(measurementManager)
- .registerWebSource(
- any<android.adservices.measurement.WebSourceRegistrationRequest>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- val request =
- WebSourceRegistrationRequest.Builder(listOf(WebSourceParams(uri1, false)), uri1)
- .setAppDestination(appDestination)
- .build()
-
- // Actually invoke the compat code.
- runBlocking { managerCompat!!.registerWebSource(request) }
-
- // Verify that the compat code was invoked correctly.
- val captor1 =
- ArgumentCaptor.forClass(
- android.adservices.measurement.WebSourceRegistrationRequest::class.java
- )
- verify(measurementManager)
- .registerWebSource(
- captor1.capture(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Verify that the request that the compat code makes to the platform is correct.
- val actualRequest = captor1.value
- assertThat(actualRequest.topOriginUri == uri1)
- assertThat(actualRequest.sourceParams.size == 1)
- assertThat(actualRequest.appDestination == appDestination)
- assertThat(actualRequest.sourceParams[0].registrationUri == uri1)
- assertThat(!actualRequest.sourceParams[0].isDebugKeyAllowed)
- }
-
- @ExperimentalFeatures.RegisterSourceOptIn
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterSource_allSuccessOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val mockInputEvent = mock(InputEvent::class.java)
- val managerCompat = obtain(mContext)
-
- val successCallback = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(3)
- receiver.onResult(Object())
- null
- }
- doAnswer(successCallback)
- .`when`(measurementManager)
- .registerSource(
- any<Uri>(),
- any<InputEvent>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- val request = SourceRegistrationRequest(listOf(uri1, uri2), mockInputEvent)
-
- // Actually invoke the compat code.
- runBlocking { managerCompat!!.registerSource(request) }
-
- // Verify that the compat code was invoked correctly.
- verify(measurementManager, times(2))
- .registerSource(
- any(),
- eq(mockInputEvent),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
- }
-
- @ExperimentalFeatures.RegisterSourceOptIn
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterSource_15thOf20Fails_remaining5DoNotExecuteOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val mockInputEvent = mock(InputEvent::class.java)
- val managerCompat = obtain(mContext)
-
- val successCallback = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(3)
- receiver.onResult(Object())
- null
- }
-
- val errorMessage = "some error occurred"
- val errorCallback = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(3)
- receiver.onError(IllegalArgumentException(errorMessage))
- null
- }
- val uris =
- (0..20)
- .map { i ->
- val uri = Uri.parse("www.uri$i.com")
- if (i == 15) {
- doAnswer(errorCallback)
- .`when`(measurementManager)
- .registerSource(
- eq(uri),
- any<InputEvent>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
- } else {
- doAnswer(successCallback)
- .`when`(measurementManager)
- .registerSource(
- eq(uri),
- any<InputEvent>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
- }
- uri
- }
- .toList()
-
- val request = SourceRegistrationRequest(uris, mockInputEvent)
-
- // Actually invoke the compat code.
- runBlocking {
- try {
- managerCompat!!.registerSource(request)
- fail("Expected failure.")
- } catch (e: IllegalArgumentException) {
- assertThat(e.message).isEqualTo(errorMessage)
- }
- }
-
- // Verify that the compat code was invoked correctly.
- (0..15).forEach { i ->
- verify(measurementManager)
- .registerSource(
- eq(Uri.parse("www.uri$i.com")),
- eq(mockInputEvent),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
- }
- (16..20).forEach { i ->
- verify(measurementManager, never())
- .registerSource(
- eq(Uri.parse("www.uri$i.com")),
- eq(mockInputEvent),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
- }
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testRegisterWebTriggerOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- val managerCompat = obtain(mContext)
- val answer = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Any, Exception>>(2)
- receiver.onResult(Object())
- null
- }
- doAnswer(answer)
- .`when`(measurementManager)
- .registerWebTrigger(
- any<android.adservices.measurement.WebTriggerRegistrationRequest>(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- val request = WebTriggerRegistrationRequest(listOf(WebTriggerParams(uri1, false)), uri2)
-
- // Actually invoke the compat code.
- runBlocking { managerCompat!!.registerWebTrigger(request) }
-
- // Verify that the compat code was invoked correctly.
- val captor1 =
- ArgumentCaptor.forClass(
- android.adservices.measurement.WebTriggerRegistrationRequest::class.java
- )
- verify(measurementManager)
- .registerWebTrigger(
- captor1.capture(),
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Any, Exception>>()
- )
-
- // Verify that the request that the compat code makes to the platform is correct.
- val actualRequest = captor1.value
- assertThat(actualRequest.destination).isEqualTo(uri2)
- assertThat(actualRequest.triggerParams.size == 1)
- assertThat(actualRequest.triggerParams[0].registrationUri == uri1)
- assertThat(!actualRequest.triggerParams[0].isDebugKeyAllowed)
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testMeasurementApiStatusOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
- callAndVerifyGetMeasurementApiStatusOnR(
- measurementManager,
- /* state= */ MeasurementManager.MEASUREMENT_API_STATE_ENABLED,
- /* expectedResult= */ MeasurementManager.MEASUREMENT_API_STATE_ENABLED
- )
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 30, minSdkVersion = 30)
- fun testMeasurementApiStatusUnknownOnR() {
- Assume.assumeTrue("minSdkVersion = API 30 ext 11", mValidAdExtServicesSdkExtVersionR)
-
- val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersionR)
-
- // Call with a value greater than values returned in SdkExtensions.AD_SERVICES = 5
- // Since the compat code does not know the returned state, it sets it to UNKNOWN.
- callAndVerifyGetMeasurementApiStatusOnR(
- measurementManager,
- /* state= */ 6,
- /* expectedResult= */ 5
- )
- }
-
@SdkSuppress(minSdkVersion = 30)
companion object {
@@ -925,38 +544,6 @@
assertThat(actualResult == expectedResult)
}
- private fun callAndVerifyGetMeasurementApiStatusOnR(
- measurementManager: android.adservices.measurement.MeasurementManager,
- state: Int,
- expectedResult: Int
- ) {
- val managerCompat = obtain(mContext)
- val answer = { args: InvocationOnMock ->
- val receiver = args.getArgument<AdServicesOutcomeReceiver<Int, Exception>>(1)
- receiver.onResult(state)
- null
- }
- doAnswer(answer)
- .`when`(measurementManager)
- .getMeasurementApiStatus(
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Int, Exception>>()
- )
-
- // Actually invoke the compat code.
- val actualResult = runBlocking { managerCompat!!.getMeasurementApiStatus() }
-
- // Verify that the compat code was invoked correctly.
- verify(measurementManager)
- .getMeasurementApiStatus(
- any<Executor>(),
- any<AdServicesOutcomeReceiver<Int, Exception>>()
- )
-
- // Verify that the request that the compat code makes to the platform is correct.
- assertThat(actualResult == expectedResult)
- }
-
private fun verifyDeletionRequest(request: android.adservices.measurement.DeletionRequest) {
// Set up the request that we expect the compat code to invoke.
val expectedRequest =
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManager.kt
index a0eb9df..df5782a 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManager.kt
@@ -57,10 +57,6 @@
BackCompatManager.getManager(context, "AdIdManager") {
AdIdManagerApi31Ext9Impl(context)
}
- } else if (AdServicesInfo.extServicesVersionR() >= 11) {
- BackCompatManager.getManager(context, "AdIdManager") {
- AdIdManagerApi30Ext11Impl(context)
- }
} else {
null
}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerApi30Ext11Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerApi30Ext11Impl.kt
deleted file mode 100644
index 5cd2bf6..0000000
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerApi30Ext11Impl.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2024 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.privacysandbox.ads.adservices.adid
-
-import android.adservices.common.AdServicesPermissions
-import android.annotation.SuppressLint
-import android.content.Context
-import android.os.Build
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresExtension
-import androidx.annotation.RequiresPermission
-import androidx.annotation.RestrictTo
-import androidx.privacysandbox.ads.adservices.internal.asAdServicesOutcomeReceiver
-import kotlinx.coroutines.suspendCancellableCoroutine
-
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-@SuppressLint("ClassVerificationFailure", "NewApi")
-@RequiresExtension(extension = Build.VERSION_CODES.R, version = 11)
-open class AdIdManagerApi30Ext11Impl(context: Context) : AdIdManager() {
- private val mAdIdManager: android.adservices.adid.AdIdManager =
- android.adservices.adid.AdIdManager.get(context)
-
- @DoNotInline
- @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_AD_ID)
- override suspend fun getAdId(): AdId {
- return convertResponse(getAdIdAsyncInternal())
- }
-
- @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_AD_ID)
- private suspend fun getAdIdAsyncInternal(): android.adservices.adid.AdId =
- suspendCancellableCoroutine { continuation ->
- mAdIdManager.getAdId(Runnable::run, continuation.asAdServicesOutcomeReceiver())
- }
-
- private fun convertResponse(response: android.adservices.adid.AdId): AdId {
- return AdId(response.adId, response.isLimitAdTrackingEnabled)
- }
-}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt
index a981294..b28fef0 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt
@@ -38,14 +38,6 @@
}
}
- fun extServicesVersionR(): Int {
- return if (Build.VERSION.SDK_INT == 30) {
- Extensions30ExtImpl.getAdExtServicesVersionR()
- } else {
- 0
- }
- }
-
@RequiresApi(30)
private object Extensions30Impl {
@DoNotInline
@@ -55,12 +47,8 @@
@RequiresApi(30)
private object Extensions30ExtImpl {
// For ExtServices, there is no AD_SERVICES extension version, so we need to check
- // for the build version. Use S for now, but this can be changed to R when we add
- // support for R later.
+ // for the build version for S.
@DoNotInline
fun getAdExtServicesVersionS() = SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S)
-
- @DoNotInline
- fun getAdExtServicesVersionR() = SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R)
}
}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesOutcomeReceiver.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesOutcomeReceiver.kt
deleted file mode 100644
index 6f87e40..0000000
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesOutcomeReceiver.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2024 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.privacysandbox.ads.adservices.internal
-
-import android.adservices.common.AdServicesOutcomeReceiver
-import android.annotation.SuppressLint
-import android.os.Build
-import androidx.annotation.RequiresExtension
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlin.coroutines.Continuation
-import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
-
-/*
- This file is a modified version OutcomeReceiver.kt in androidx.core.os, designed to provide the same
- functionality with the AdServicesOutcomeReceiver, to keep the implementation of the backward compatible
- classes as close to identical as possible.
-*/
-
-@RequiresExtension(extension = Build.VERSION_CODES.R, version = 11)
-fun <R, E : Throwable> Continuation<R>.asAdServicesOutcomeReceiver():
- AdServicesOutcomeReceiver<R, E> = ContinuationOutcomeReceiver(this)
-
-@SuppressLint("NewApi")
-@RequiresExtension(extension = Build.VERSION_CODES.R, version = 11)
-private class ContinuationOutcomeReceiver<R, E : Throwable>(
- private val continuation: Continuation<R>
-) : AdServicesOutcomeReceiver<R, E>, AtomicBoolean(false) {
- @Suppress("WRONG_NULLABILITY_FOR_JAVA_OVERRIDE")
- override fun onResult(result: R) {
- // Do not attempt to resume more than once, even if the caller of the returned
- // OutcomeReceiver is buggy and tries anyway.
- if (compareAndSet(false, true)) {
- continuation.resume(result)
- }
- }
-
- override fun onError(error: E) {
- // Do not attempt to resume more than once, even if the caller of the returned
- // OutcomeReceiver is buggy and tries anyway.
- if (compareAndSet(false, true)) {
- continuation.resumeWithException(error)
- }
- }
-
- override fun toString() = "ContinuationOutcomeReceiver(outcomeReceived = ${get()})"
-}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/BackCompatManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/BackCompatManager.kt
index f0f6005..f796274 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/BackCompatManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/BackCompatManager.kt
@@ -29,7 +29,6 @@
Log.d(
tag,
"Unable to find adservices code, check manifest for uses-library tag, " +
- "versionR=${AdServicesInfo.extServicesVersionR()}, " +
"versionS=${AdServicesInfo.extServicesVersionS()}"
)
return null
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/DeletionRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/DeletionRequest.kt
index a482855..14782a9 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/DeletionRequest.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/DeletionRequest.kt
@@ -100,7 +100,6 @@
@SuppressLint("ClassVerificationFailure", "NewApi")
@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
- @RequiresExtension(extension = Build.VERSION_CODES.R, version = 11)
internal fun convertToAdServices(): android.adservices.measurement.DeletionRequest {
return android.adservices.measurement.DeletionRequest.Builder()
.setDeletionMode(deletionMode)
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
index ebfdc16..ea8584b 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
@@ -149,10 +149,6 @@
BackCompatManager.getManager(context, "MeasurementManager") {
MeasurementManagerApi31Ext9Impl(context)
}
- } else if (AdServicesInfo.extServicesVersionR() >= 11) {
- BackCompatManager.getManager(context, "MeasurementManager") {
- MeasurementManagerApi30Ext11Impl(context)
- }
} else {
null
}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerApi30Ext11Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerApi30Ext11Impl.kt
deleted file mode 100644
index 86ed67c..0000000
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerApi30Ext11Impl.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright 2024 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.privacysandbox.ads.adservices.measurement
-
-import android.adservices.common.AdServicesPermissions
-import android.annotation.SuppressLint
-import android.content.Context
-import android.net.Uri
-import android.os.Build
-import android.view.InputEvent
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresExtension
-import androidx.annotation.RequiresPermission
-import androidx.annotation.RestrictTo
-import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
-import androidx.privacysandbox.ads.adservices.internal.asAdServicesOutcomeReceiver
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.suspendCancellableCoroutine
-
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-@SuppressLint("ClassVerificationFailure", "NewApi")
-@RequiresExtension(extension = Build.VERSION_CODES.R, version = 11)
-class MeasurementManagerApi30Ext11Impl(context: Context) : MeasurementManager() {
- private val mMeasurementManager: android.adservices.measurement.MeasurementManager =
- android.adservices.measurement.MeasurementManager.get(context)
-
- @DoNotInline
- override suspend fun deleteRegistrations(deletionRequest: DeletionRequest) {
- suspendCancellableCoroutine<Any> { continuation ->
- mMeasurementManager.deleteRegistrations(
- deletionRequest.convertToAdServices(),
- Runnable::run,
- continuation.asAdServicesOutcomeReceiver()
- )
- }
- }
-
- @DoNotInline
- @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
- override suspend fun registerSource(attributionSource: Uri, inputEvent: InputEvent?) {
- suspendCancellableCoroutine<Any> { continuation ->
- mMeasurementManager.registerSource(
- attributionSource,
- inputEvent,
- Runnable::run,
- continuation.asAdServicesOutcomeReceiver()
- )
- }
- }
-
- @DoNotInline
- @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
- override suspend fun registerTrigger(trigger: Uri) {
- suspendCancellableCoroutine<Any> { continuation ->
- mMeasurementManager.registerTrigger(
- trigger,
- Runnable::run,
- continuation.asAdServicesOutcomeReceiver()
- )
- }
- }
-
- @DoNotInline
- @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
- override suspend fun registerWebSource(request: WebSourceRegistrationRequest) {
- suspendCancellableCoroutine<Any> { continuation ->
- mMeasurementManager.registerWebSource(
- request.convertToAdServices(),
- Runnable::run,
- continuation.asAdServicesOutcomeReceiver()
- )
- }
- }
-
- @DoNotInline
- @ExperimentalFeatures.RegisterSourceOptIn
- @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
- override suspend fun registerSource(request: SourceRegistrationRequest): Unit = coroutineScope {
- request.registrationUris.forEach { uri ->
- launch {
- suspendCancellableCoroutine<Any> { continuation ->
- mMeasurementManager.registerSource(
- uri,
- request.inputEvent,
- Runnable::run,
- continuation.asAdServicesOutcomeReceiver()
- )
- }
- }
- }
- }
-
- @DoNotInline
- @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
- override suspend fun registerWebTrigger(request: WebTriggerRegistrationRequest) {
- suspendCancellableCoroutine<Any> { continuation ->
- mMeasurementManager.registerWebTrigger(
- request.convertToAdServices(),
- Runnable::run,
- continuation.asAdServicesOutcomeReceiver()
- )
- }
- }
-
- @DoNotInline
- @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
- override suspend fun getMeasurementApiStatus(): Int =
- suspendCancellableCoroutine { continuation ->
- mMeasurementManager.getMeasurementApiStatus(
- Runnable::run,
- continuation.asAdServicesOutcomeReceiver()
- )
- }
-}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebSourceParams.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebSourceParams.kt
index c7bc6fe..1d3218f 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebSourceParams.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebSourceParams.kt
@@ -54,7 +54,6 @@
@SuppressLint("ClassVerificationFailure", "NewApi")
@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
- @RequiresExtension(extension = Build.VERSION_CODES.R, version = 11)
internal fun convertWebSourceParams(
request: List<WebSourceParams>
): List<android.adservices.measurement.WebSourceParams> {
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebSourceRegistrationRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebSourceRegistrationRequest.kt
index 5d48214..6499e73 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebSourceRegistrationRequest.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebSourceRegistrationRequest.kt
@@ -95,7 +95,6 @@
@SuppressLint("ClassVerificationFailure", "NewApi")
@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
- @RequiresExtension(extension = Build.VERSION_CODES.R, version = 11)
internal fun convertToAdServices():
android.adservices.measurement.WebSourceRegistrationRequest {
return android.adservices.measurement.WebSourceRegistrationRequest.Builder(
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebTriggerParams.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebTriggerParams.kt
index 2163701..bc3eefc 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebTriggerParams.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebTriggerParams.kt
@@ -54,7 +54,6 @@
@SuppressLint("ClassVerificationFailure", "NewApi")
@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
- @RequiresExtension(extension = Build.VERSION_CODES.R, version = 11)
internal fun convertWebTriggerParams(
request: List<WebTriggerParams>
): List<android.adservices.measurement.WebTriggerParams> {
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebTriggerRegistrationRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebTriggerRegistrationRequest.kt
index f521339..7384929 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebTriggerRegistrationRequest.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/WebTriggerRegistrationRequest.kt
@@ -52,7 +52,6 @@
@SuppressLint("ClassVerificationFailure", "NewApi")
@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
- @RequiresExtension(extension = Build.VERSION_CODES.R, version = 11)
internal fun convertToAdServices():
android.adservices.measurement.WebTriggerRegistrationRequest {
return android.adservices.measurement.WebTriggerRegistrationRequest.Builder(
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/build.gradle b/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
index bb5cec2..fb5e7c0 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
+++ b/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
@@ -75,7 +75,7 @@
}
androidComponents {
- onVariants(selector().withBuildType("debug")) {
+ onVariants(selector().withBuildType("release")) {
androidTest.sources.assets.addGeneratedSourceDirectory(
bundleTestSdkDexTaskProvider,
BundleTestSdkDexTask::getOutputDir
diff --git a/privacysandbox/tools/tools-apicompiler/build.gradle b/privacysandbox/tools/tools-apicompiler/build.gradle
index ec701e3..27258e4 100644
--- a/privacysandbox/tools/tools-apicompiler/build.gradle
+++ b/privacysandbox/tools/tools-apicompiler/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import androidx.build.SdkHelperKt
import androidx.build.AndroidXConfig
@@ -80,4 +82,5 @@
type = LibraryType.ANNOTATION_PROCESSOR
inceptionYear = "2022"
description = "Compiler for Privacy Sandbox API annotations."
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt
index 1e0e45f..25f5c7b 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt
@@ -59,6 +59,22 @@
) {
logger.error("Error in $name: annotated interfaces cannot declare companion objects.")
}
+ if (
+ interfaceDeclaration.declarations
+ .filterIsInstance<KSClassDeclaration>()
+ .filter {
+ listOf(
+ ClassKind.OBJECT,
+ ClassKind.INTERFACE,
+ ClassKind.ENUM_CLASS,
+ ClassKind.CLASS
+ )
+ .contains(it.classKind)
+ }
+ .any { !it.isCompanionObject }
+ ) {
+ logger.error("Error in $name: annotated interfaces cannot declare objects or classes.")
+ }
val invalidModifiers =
interfaceDeclaration.modifiers.filterNot(validInterfaceModifiers::contains)
if (invalidModifiers.isNotEmpty()) {
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParser.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParser.kt
index 8811255..6f548dc 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParser.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParser.kt
@@ -51,6 +51,7 @@
logger.error("Error in $name: annotated values should be public.")
}
ensureNoCompanion(value, name)
+ ensureNoObject(value, name)
ensureNoTypeParameters(value, name)
ensureNoSuperTypes(value, name)
@@ -86,6 +87,25 @@
}
}
+ private fun ensureNoObject(classDeclaration: KSClassDeclaration, name: String) {
+ if (
+ classDeclaration.declarations
+ .filterIsInstance<KSClassDeclaration>()
+ .filter {
+ listOf(
+ ClassKind.OBJECT,
+ ClassKind.INTERFACE,
+ ClassKind.ENUM_CLASS,
+ ClassKind.CLASS
+ )
+ .contains(it.classKind)
+ }
+ .any { !it.isCompanionObject }
+ ) {
+ logger.error("Error in $name: annotated values cannot declare objects or classes.")
+ }
+ }
+
private fun ensureNoTypeParameters(classDeclaration: KSClassDeclaration, name: String) {
if (classDeclaration.typeParameters.isNotEmpty()) {
logger.error(
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt
index 9cb0abf..975ee9a 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt
@@ -256,6 +256,71 @@
}
@Test
+ fun interfaceWithObject_fails() {
+ checkSourceFails(
+ serviceInterface(
+ """public interface MySdk {
+ | object MyObject {
+ | }
+ |}
+ """
+ .trimMargin()
+ )
+ )
+ .containsExactlyErrors(
+ "Error in com.mysdk.MySdk: annotated interfaces cannot declare objects or classes."
+ )
+ }
+
+ @Test
+ fun interfaceWithEnumClass_fails() {
+ checkSourceFails(
+ serviceInterface(
+ """public interface MySdk {
+ | enum class MyEnumClass {}
+ |}
+ """
+ .trimMargin()
+ )
+ )
+ .containsExactlyErrors(
+ "Error in com.mysdk.MySdk: annotated interfaces cannot declare objects or classes."
+ )
+ }
+
+ @Test
+ fun interfaceWithInterface_fails() {
+ checkSourceFails(
+ serviceInterface(
+ """public interface MySdk {
+ | private interface MyInterface {}
+ |}
+ """
+ .trimMargin()
+ )
+ )
+ .containsExactlyErrors(
+ "Error in com.mysdk.MySdk: annotated interfaces cannot declare objects or classes."
+ )
+ }
+
+ @Test
+ fun interfaceWithInnerClass_fails() {
+ checkSourceFails(
+ serviceInterface(
+ """public interface MySdk {
+ | class MyInnerClass {}
+ |}
+ """
+ .trimMargin()
+ )
+ )
+ .containsExactlyErrors(
+ "Error in com.mysdk.MySdk: annotated interfaces cannot declare objects or classes."
+ )
+ }
+
+ @Test
fun interfaceWithInvalidModifier_fails() {
checkSourceFails(
serviceInterface(
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt
index 3a486ea..88949a2 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt
@@ -218,6 +218,64 @@
}
@Test
+ fun dataClassWithObject_fails() {
+ val dataClass =
+ annotatedValue(
+ """
+ |data class MySdkRequest(val id: Int) {
+ | object Constants {
+ | val someConstant = 12
+ | }
+ |}
+ """
+ .trimMargin()
+ )
+ checkSourceFails(dataClass)
+ .containsExactlyErrors(
+ "Error in com.mysdk.MySdkRequest: annotated values cannot declare objects or " +
+ "classes."
+ )
+ }
+
+ @Test
+ fun dataClassWithInnerClass_fails() {
+ val dataClass =
+ annotatedValue(
+ """
+ |data class MySdkRequest(val id: Int) {
+ | class MyClass {
+ | val someConstant = 12
+ | }
+ |}
+ """
+ .trimMargin()
+ )
+ checkSourceFails(dataClass)
+ .containsExactlyErrors(
+ "Error in com.mysdk.MySdkRequest: annotated values cannot declare objects or " +
+ "classes."
+ )
+ }
+
+ @Test
+ fun dataClassWithEnumClass_fails() {
+ val dataClass =
+ annotatedValue(
+ """
+ |data class MySdkRequest(val id: Int) {
+ | enum class MyClass { RED, GREEN }
+ |}
+ """
+ .trimMargin()
+ )
+ checkSourceFails(dataClass)
+ .containsExactlyErrors(
+ "Error in com.mysdk.MySdkRequest: annotated values cannot declare objects or " +
+ "classes."
+ )
+ }
+
+ @Test
fun dataClassWithTypeParameters_fails() {
val dataClass = annotatedValue("data class MySdkRequest<T>(val id: Int, val data: T)")
checkSourceFails(dataClass)
diff --git a/privacysandbox/ui/integration-tests/sdkproviderutils/build.gradle b/privacysandbox/ui/integration-tests/sdkproviderutils/build.gradle
index 2a9b90d9c..323d9a0 100644
--- a/privacysandbox/ui/integration-tests/sdkproviderutils/build.gradle
+++ b/privacysandbox/ui/integration-tests/sdkproviderutils/build.gradle
@@ -29,4 +29,7 @@
implementation project(':privacysandbox:ui:ui-provider')
implementation project(':privacysandbox:ui:integration-tests:testaidl')
implementation project(':webkit:webkit')
+ implementation(libs.media3Ui)
+ implementation(libs.media3Common)
+ implementation(libs.media3Exoplayer)
}
diff --git a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/MediateeSdkApiImpl.kt b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/MediateeSdkApiImpl.kt
index e647c7a..c19d38b 100644
--- a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/MediateeSdkApiImpl.kt
+++ b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/MediateeSdkApiImpl.kt
@@ -40,8 +40,9 @@
): Bundle {
val adapter: SandboxedUiAdapter =
when (adType) {
- AdType.WEBVIEW -> loadWebViewBannerAd()
+ AdType.BASIC_WEBVIEW -> loadWebViewBannerAd()
AdType.WEBVIEW_FROM_LOCAL_ASSETS -> loadWebViewBannerAdFromLocalAssets()
+ AdType.NON_WEBVIEW_VIDEO -> loadVideoAd()
else -> loadNonWebViewBannerAd(mediationDescription, waitInsideOnDraw)
}
ViewabilityHandler.addObserverFactoryToAdapter(adapter, drawViewability)
@@ -56,6 +57,13 @@
return testAdapters.WebViewAdFromLocalAssets()
}
+ private fun loadVideoAd(): SandboxedUiAdapter {
+ val playerViewProvider = PlayerViewProvider()
+ val adapter = testAdapters.VideoBannerAd(playerViewProvider)
+ PlayerViewabilityHandler.addObserverFactoryToAdapter(adapter, playerViewProvider)
+ return adapter
+ }
+
private fun loadNonWebViewBannerAd(
text: String,
waitInsideOnDraw: Boolean
diff --git a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/PlayerViewProvider.kt b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/PlayerViewProvider.kt
new file mode 100644
index 0000000..efa13eb
--- /dev/null
+++ b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/PlayerViewProvider.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2024 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.privacysandbox.ui.integration.sdkproviderutils
+
+import android.content.Context
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import android.view.View
+import androidx.media3.common.AudioAttributes
+import androidx.media3.common.C
+import androidx.media3.common.MediaItem
+import androidx.media3.common.Player
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.ui.PlayerView
+import java.util.WeakHashMap
+
+/** Create PlayerView with Player and controlling playback based on player visibility. */
+class PlayerViewProvider {
+
+ private val handler = Handler(Looper.getMainLooper())
+ private val createdViews = WeakHashMap<PlayerView, PlayerWithState>()
+
+ fun createPlayerView(windowContext: Context, videoUrl: String): View {
+ val viewId = View.generateViewId()
+
+ val view = PlayerView(windowContext)
+ view.id = viewId
+
+ val playerWithState = PlayerWithState(windowContext, videoUrl)
+ createdViews[view] = playerWithState
+
+ return view
+ }
+
+ fun onPlayerVisible(id: Int) {
+ Log.i(TAG, "onPlayerVisible: $id")
+ handler.post {
+ for ((view: PlayerView, state: PlayerWithState) in createdViews) {
+ if (view.player == null) {
+ val player = state.initializePlayer()
+ view.setPlayer(player)
+ }
+ if (view.id == id && view.player?.isPlaying == false) {
+ Log.i(TAG, "onPlayerVisible: resuming $id")
+ view.player?.play()
+ }
+ }
+ }
+ }
+
+ fun onPlayerInvisible(id: Int) {
+ Log.i(TAG, "onPlayerInVisible: $id")
+ handler.post {
+ for ((view: PlayerView, _: PlayerWithState) in createdViews) {
+ if (view.id == id) {
+ Log.i(TAG, "onPlayerInVisible: pausing $id")
+ view.player?.pause()
+ }
+ }
+ }
+ }
+
+ fun onSessionClosed() {
+ Log.i(TAG, "onSessionClosed, releasing player resources")
+ handler.post {
+ for ((view: PlayerView, state: PlayerWithState) in createdViews) {
+ state.releasePlayer()
+ view.setPlayer(null)
+ }
+ }
+ createdViews.clear()
+ }
+
+ inner class PlayerWithState(private val context: Context, private val videoUrl: String) {
+ private var player: ExoPlayer? = null
+ private var autoPlay = true
+ private var autoPlayPosition: Long
+
+ init {
+ autoPlayPosition = C.TIME_UNSET
+ }
+
+ fun initializePlayer(): Player? {
+ player?.let {
+ return it
+ }
+
+ val audioAttributes =
+ AudioAttributes.Builder()
+ .setUsage(C.USAGE_MEDIA)
+ .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
+ .build()
+
+ player = ExoPlayer.Builder(context).setAudioAttributes(audioAttributes, true).build()
+ player?.apply {
+ setPlayWhenReady(autoPlay)
+ setMediaItem(MediaItem.fromUri(Uri.parse(videoUrl)))
+ val hasStartPosition = autoPlayPosition != C.TIME_UNSET
+ if (hasStartPosition) {
+ seekTo(0, autoPlayPosition)
+ }
+ prepare()
+ }
+
+ return player
+ }
+
+ fun releasePlayer() {
+ player?.run {
+ autoPlay = playWhenReady
+ autoPlayPosition = contentPosition
+ release()
+ }
+ player = null
+ }
+ }
+
+ private companion object {
+ const val TAG = "PlayerViewProvider"
+ }
+}
diff --git a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/PlayerViewabilityHandler.kt b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/PlayerViewabilityHandler.kt
new file mode 100644
index 0000000..25a3851
--- /dev/null
+++ b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/PlayerViewabilityHandler.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2024 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.privacysandbox.ui.integration.sdkproviderutils
+
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.privacysandbox.ui.core.SandboxedSdkViewUiInfo
+import androidx.privacysandbox.ui.core.SandboxedUiAdapter
+import androidx.privacysandbox.ui.core.SessionObserver
+import androidx.privacysandbox.ui.core.SessionObserverContext
+import androidx.privacysandbox.ui.core.SessionObserverFactory
+
+class PlayerViewabilityHandler {
+
+ private class SessionObserverFactoryImpl(val playerViewProvider: PlayerViewProvider) :
+ SessionObserverFactory {
+
+ override fun create(): SessionObserver {
+ return SessionObserverImpl(playerViewProvider)
+ }
+
+ private inner class SessionObserverImpl(val playerViewProvider: PlayerViewProvider) :
+ SessionObserver {
+ lateinit var view: View
+ var isPlayerVisible = false
+
+ override fun onSessionOpened(sessionObserverContext: SessionObserverContext) {
+ Log.i(TAG, "onSessionOpened $sessionObserverContext")
+ view = checkNotNull(sessionObserverContext.view)
+ }
+
+ override fun onUiContainerChanged(uiContainerInfo: Bundle) {
+ val sandboxedSdkViewUiInfo = SandboxedSdkViewUiInfo.fromBundle(uiContainerInfo)
+ Log.i(TAG, "onUiContainerChanged $sandboxedSdkViewUiInfo")
+
+ val updatedVisibility = !sandboxedSdkViewUiInfo.onScreenGeometry.isEmpty
+ if (updatedVisibility != isPlayerVisible) {
+ Log.i(
+ TAG,
+ "Video player previous visibility $isPlayerVisible, updated visibility $updatedVisibility"
+ )
+ isPlayerVisible = updatedVisibility
+ if (isPlayerVisible) {
+ playerViewProvider.onPlayerVisible(view.id)
+ } else {
+ playerViewProvider.onPlayerInvisible(view.id)
+ }
+ }
+ }
+
+ override fun onSessionClosed() {
+ Log.i(TAG, "session closed")
+ playerViewProvider.onSessionClosed()
+ }
+ }
+ }
+
+ companion object {
+
+ private val TAG = PlayerViewabilityHandler::class.simpleName
+
+ fun addObserverFactoryToAdapter(
+ adapter: SandboxedUiAdapter,
+ playerViewProvider: PlayerViewProvider
+ ) {
+ return adapter.addObserverFactory(SessionObserverFactoryImpl(playerViewProvider))
+ }
+ }
+}
diff --git a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/SdkApiConstants.kt b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/SdkApiConstants.kt
index 19b8b01..66da2c5 100644
--- a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/SdkApiConstants.kt
+++ b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/SdkApiConstants.kt
@@ -21,9 +21,10 @@
companion object {
annotation class AdType {
companion object {
- const val NON_WEBVIEW = 0
- const val WEBVIEW = 1
+ const val BASIC_NON_WEBVIEW = 0
+ const val BASIC_WEBVIEW = 1
const val WEBVIEW_FROM_LOCAL_ASSETS = 2
+ const val NON_WEBVIEW_VIDEO = 3
}
}
diff --git a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt
index 7c41a22..b613689 100644
--- a/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt
+++ b/privacysandbox/ui/integration-tests/sdkproviderutils/src/main/java/androidx/privacysandbox/ui/integration/sdkproviderutils/TestAdapters.kt
@@ -126,6 +126,16 @@
}
}
+ inner class VideoBannerAd(private val playerViewProvider: PlayerViewProvider) : BannerAd() {
+
+ override fun buildAdView(sessionContext: Context): View {
+ return playerViewProvider.createPlayerView(
+ sessionContext,
+ "https://html5demos.com/assets/dizzy.mp4"
+ )
+ }
+ }
+
inner class WebViewAdFromLocalAssets : BannerAd() {
override fun buildAdView(sessionContext: Context): View {
val webView = WebView(sessionContext)
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt
index 47a3592..86fed41 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/BaseFragment.kt
@@ -135,7 +135,7 @@
"androidx.privacysandbox.ui.integration.mediateesdkprovider"
const val TAG = "TestSandboxClient"
var isZOrderOnTop = true
- @AdType var currentAdType = AdType.NON_WEBVIEW
+ @AdType var currentAdType = AdType.BASIC_NON_WEBVIEW
@MediationOption var currentMediationOption = MediationOption.NON_MEDIATED
var shouldDrawViewabilityLayer = false
}
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
index a54d6f1..02d4cec 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
@@ -49,12 +49,13 @@
private lateinit var navigationView: NavigationView
private lateinit var currentFragment: BaseFragment
private lateinit var triggerSandboxDeathButton: Button
- private lateinit var webViewToggleButton: SwitchMaterial
private lateinit var zOrderToggleButton: SwitchMaterial
- private lateinit var contentFromAssetsToggleButton: SwitchMaterial
private lateinit var viewabilityToggleButton: SwitchMaterial
private lateinit var mediationDropDownMenu: Spinner
- @AdType private var adType = AdType.NON_WEBVIEW
+ private lateinit var adTypeDropDownMenu: Spinner
+
+ @AdType private var adType = AdType.BASIC_NON_WEBVIEW
+
@MediationOption private var mediationOption = MediationOption.NON_MEDIATED
private var drawViewabilityLayer = false
@@ -65,12 +66,11 @@
setContentView(R.layout.activity_main)
drawerLayout = findViewById(R.id.drawer)
navigationView = findViewById(R.id.navigation_view)
- contentFromAssetsToggleButton = findViewById(R.id.content_from_assets_switch)
zOrderToggleButton = findViewById(R.id.zorder_below_switch)
- webViewToggleButton = findViewById(R.id.load_webview)
viewabilityToggleButton = findViewById(R.id.display_viewability_switch)
triggerSandboxDeathButton = findViewById(R.id.trigger_sandbox_death)
mediationDropDownMenu = findViewById(R.id.mediation_dropdown_menu)
+ adTypeDropDownMenu = findViewById(R.id.ad_type_dropdown_menu)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// there is no sandbox to kill on T-
@@ -139,59 +139,26 @@
}
private fun initializeToggles() {
- initializeWebViewToggleSwitch()
- initializeContentFromAssetsToggleButton()
initializeViewabilityToggleButton()
initializeMediationDropDown()
+ initializeAdTypeDropDown()
initializeZOrderToggleButton()
}
private fun disableAllControls() {
- webViewToggleButton.isEnabled = false
- contentFromAssetsToggleButton.isEnabled = false
mediationDropDownMenu.isEnabled = false
+ adTypeDropDownMenu.isEnabled = false
viewabilityToggleButton.isEnabled = false
zOrderToggleButton.isEnabled = false
}
private fun enableAllControls() {
- webViewToggleButton.isEnabled = true
- contentFromAssetsToggleButton.isEnabled = webViewToggleButton.isChecked
mediationDropDownMenu.isEnabled = true
+ adTypeDropDownMenu.isEnabled = true
viewabilityToggleButton.isEnabled = true
zOrderToggleButton.isEnabled = true
}
- private fun initializeWebViewToggleSwitch() {
- contentFromAssetsToggleButton.isEnabled = false
- webViewToggleButton.setOnCheckedChangeListener { _, isChecked ->
- contentFromAssetsToggleButton.isEnabled = isChecked
- adType =
- if (isChecked) {
- if (contentFromAssetsToggleButton.isChecked) {
- AdType.WEBVIEW_FROM_LOCAL_ASSETS
- } else {
- AdType.WEBVIEW
- }
- } else {
- AdType.NON_WEBVIEW
- }
- loadAllAds()
- }
- }
-
- private fun initializeContentFromAssetsToggleButton() {
- contentFromAssetsToggleButton.setOnCheckedChangeListener { _, isChecked ->
- adType =
- if (isChecked) {
- AdType.WEBVIEW_FROM_LOCAL_ASSETS
- } else {
- AdType.WEBVIEW
- }
- loadAllAds()
- }
- }
-
private fun initializeViewabilityToggleButton() {
viewabilityToggleButton.setOnCheckedChangeListener { _, isChecked ->
drawViewabilityLayer = isChecked
@@ -252,6 +219,46 @@
}
}
+ private fun initializeAdTypeDropDown() {
+ ArrayAdapter.createFromResource(
+ applicationContext,
+ R.array.ad_type_dropdown_menu_array,
+ android.R.layout.simple_spinner_item
+ )
+ .also { adapter ->
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ adTypeDropDownMenu.adapter = adapter
+ }
+
+ adTypeDropDownMenu.onItemSelectedListener =
+ object : AdapterView.OnItemSelectedListener {
+ var isCalledOnStartingApp = true
+
+ override fun onItemSelected(
+ parent: AdapterView<*>?,
+ view: View?,
+ position: Int,
+ selectedAdOptionId: Long
+ ) {
+ if (isCalledOnStartingApp) {
+ isCalledOnStartingApp = false
+ return
+ }
+ adType =
+ when (position) {
+ 0 -> AdType.BASIC_NON_WEBVIEW
+ 1 -> AdType.BASIC_WEBVIEW
+ 2 -> AdType.WEBVIEW_FROM_LOCAL_ASSETS
+ 3 -> AdType.NON_WEBVIEW_VIDEO
+ else -> AdType.BASIC_NON_WEBVIEW
+ }
+ loadAllAds()
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {}
+ }
+ }
+
private fun initializeZOrderToggleButton() {
zOrderToggleButton.setOnCheckedChangeListener { _, isChecked ->
BaseFragment.isZOrderOnTop = !isChecked
@@ -264,7 +271,6 @@
if (drawerLayout.isOpen) {
drawerLayout.closeDrawers()
} else {
- currentFragment.handleDrawerStateChange(true)
drawerLayout.open()
}
}
@@ -273,14 +279,20 @@
private fun initializeDrawer() {
drawerLayout.addDrawerListener(
object : DrawerListener {
- override fun onDrawerSlide(drawerView: View, slideOffset: Float) {}
+ private var isDrawerOpen = false
- override fun onDrawerOpened(drawerView: View) {
- // we handle this in the button onClick instead
+ override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
+ if (!isDrawerOpen) {
+ isDrawerOpen = true
+ currentFragment.handleDrawerStateChange(isDrawerOpen = true)
+ }
}
+ override fun onDrawerOpened(drawerView: View) {}
+
override fun onDrawerClosed(drawerView: View) {
- currentFragment.handleDrawerStateChange(false)
+ isDrawerOpen = false
+ currentFragment.handleDrawerStateChange(isDrawerOpen = false)
}
override fun onDrawerStateChanged(newState: Int) {}
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
index 0779727..6b72533 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -16,33 +16,38 @@
-->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
+
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
+
<Button
+ android:id="@+id/toggle_drawer_button"
android:layout_width="match_parent"
android:layout_height="50dp"
- android:id="@+id/toggle_drawer_button"
- android:text="Open Options"/>
+ android:text="Open Options" />
+
<FrameLayout
android:id="@+id/content_fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1"/>
+ android:layout_weight="1" />
</LinearLayout>
+
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="@layout/action_menu">
+
<Spinner
android:id="@+id/mediation_dropdown_menu"
android:layout_width="match_parent"
@@ -50,44 +55,40 @@
android:layout_gravity="center"
android:background="@android:drawable/btn_dropdown"
android:spinnerMode="dropdown" />
- <com.google.android.material.switchmaterial.SwitchMaterial
- android:id="@+id/load_webview"
- android:layout_width="wrap_content"
+
+ <Spinner
+ android:id="@+id/ad_type_dropdown_menu"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_gravity="center"
android:layout_marginTop="50dp"
- android:layout_gravity="center"
- android:text="@string/webview_switch"
- android:checked="false" />
- <com.google.android.material.switchmaterial.SwitchMaterial
- android:id="@+id/content_from_assets_switch"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="100dp"
- android:layout_gravity="center"
- android:text="@string/content_from_assets_switch"
- android:checked="false" />
+ android:background="@android:drawable/btn_dropdown"
+ android:spinnerMode="dropdown" />
+
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/display_viewability_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="150dp"
android:layout_gravity="center"
- android:text="@string/display_viewability_switch"
- android:checked="false"/>
+ android:layout_marginTop="100dp"
+ android:checked="false"
+ android:text="@string/display_viewability_switch" />
+
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/zorder_below_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="200dp"
android:layout_gravity="center"
- android:text="@string/zorder_below_switch"
- android:checked="false" />
+ android:layout_marginTop="150dp"
+ android:checked="false"
+ android:text="@string/zorder_below_switch" />
+
<Button
+ android:id="@+id/trigger_sandbox_death"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="250dp"
android:layout_gravity="center"
- android:id="@+id/trigger_sandbox_death"
- android:text="@string/trigger_sandbox_death_button"/>
+ android:layout_marginTop="200dp"
+ android:text="@string/trigger_sandbox_death_button" />
</com.google.android.material.navigation.NavigationView>
</androidx.drawerlayout.widget.DrawerLayout>
diff --git a/pdf/pdf-viewer/src/main/res/values/styles.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/values/ad_type_options.xml
similarity index 68%
rename from pdf/pdf-viewer/src/main/res/values/styles.xml
rename to privacysandbox/ui/integration-tests/testapp/src/main/res/values/ad_type_options.xml
index e752dd2..be23644 100644
--- a/pdf/pdf-viewer/src/main/res/values/styles.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/values/ad_type_options.xml
@@ -14,12 +14,11 @@
limitations under the License.
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Label">
- <item name="android:textColor">@color/text_default</item>
- </style>
-
- <style name="TextField" />
-
+<resources>
+ <string-array name="ad_type_dropdown_menu_array">
+ <item>@string/basic_non_webview_switch</item>
+ <item>@string/basic_webview_switch</item>
+ <item>@string/content_from_assets_switch</item>
+ <item>@string/video_switch</item>
+ </string-array>
</resources>
\ No newline at end of file
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
index ff7300f..3bddd28 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/values/strings.xml
@@ -25,7 +25,9 @@
<string name="zorder_below_switch">Z Order Below</string>
<string name="mediation_switch">Mediation</string>
<string name="app_owned_mediatee_switch">AppOwnedMediatee</string>
- <string name="webview_switch">Webview</string>
+ <string name="basic_webview_switch">Basic Webview</string>
<string name="trigger_sandbox_death_button">Trigger Sandbox Death</string>
<string name="display_viewability_switch">Display Viewability Geometry</string>
+ <string name="video_switch">Video</string>
+ <string name="basic_non_webview_switch">Basic Non-Webview</string>
</resources>
diff --git a/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt b/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt
index 054ec2d..53534f9 100644
--- a/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt
+++ b/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt
@@ -21,6 +21,8 @@
import android.os.Process
import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
+import androidx.privacysandbox.ui.integration.sdkproviderutils.PlayerViewProvider
+import androidx.privacysandbox.ui.integration.sdkproviderutils.PlayerViewabilityHandler
import androidx.privacysandbox.ui.integration.sdkproviderutils.SdkApiConstants.Companion.AdType
import androidx.privacysandbox.ui.integration.sdkproviderutils.SdkApiConstants.Companion.MediationOption
import androidx.privacysandbox.ui.integration.sdkproviderutils.TestAdapters
@@ -52,15 +54,16 @@
}
val adapter: SandboxedUiAdapter =
when (adType) {
- AdType.NON_WEBVIEW -> {
+ AdType.BASIC_NON_WEBVIEW -> {
loadNonWebViewBannerAd("Simple Ad", waitInsideOnDraw)
}
- AdType.WEBVIEW -> {
+ AdType.BASIC_WEBVIEW -> {
loadWebViewBannerAd()
}
AdType.WEBVIEW_FROM_LOCAL_ASSETS -> {
loadWebViewBannerAdFromLocalAssets()
}
+ AdType.NON_WEBVIEW_VIDEO -> loadVideoAd()
else -> {
loadNonWebViewBannerAd("Ad type not present", waitInsideOnDraw)
}
@@ -88,6 +91,13 @@
return testAdapters.TestBannerAd(text, waitInsideOnDraw)
}
+ private fun loadVideoAd(): SandboxedUiAdapter {
+ val playerViewProvider = PlayerViewProvider()
+ val adapter = testAdapters.VideoBannerAd(playerViewProvider)
+ PlayerViewabilityHandler.addObserverFactoryToAdapter(adapter, playerViewProvider)
+ return adapter
+ }
+
override fun requestResize(width: Int, height: Int) {}
private fun maybeGetMediateeBannerAdBundle(
diff --git a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
index 5bec1b4..9287951 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
+++ b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
@@ -181,7 +181,7 @@
view.layoutParams = LinearLayout.LayoutParams(initialWidth, initialHeight)
}
- fun requestSizeChange(width: Int, height: Int) {
+ fun requestResize(width: Int, height: Int) {
internalClient?.onResizeRequested(width, height)
}
@@ -481,7 +481,7 @@
layout.addView(view)
}
testSandboxedUiAdapter.assertSessionOpened()
- testSandboxedUiAdapter.testSession?.requestSizeChange(layout.width, layout.height)
+ testSandboxedUiAdapter.testSession?.requestResize(layout.width, layout.height)
val observer = view.viewTreeObserver
observer.addOnGlobalLayoutListener(
object : ViewTreeObserver.OnGlobalLayoutListener {
@@ -617,10 +617,10 @@
@Ignore("b/307829956")
@Test
- fun requestSizeWithMeasureSpecAtMost_withinParentBounds() {
+ fun requestResizeWithMeasureSpecAtMost_withinParentBounds() {
view.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
addViewToLayoutAndWaitToBeActive()
- requestSizeAndVerifyLayout(
+ requestResizeAndVerifyLayout(
/* requestedWidth=*/ mainLayoutWidth - 100,
/* requestedHeight=*/ mainLayoutHeight - 100,
/* expectedWidth=*/ mainLayoutWidth - 100,
@@ -629,11 +629,11 @@
}
@Test
- fun requestSizeWithMeasureSpecAtMost_exceedsParentBounds() {
+ fun requestResizeWithMeasureSpecAtMost_exceedsParentBounds() {
view.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
addViewToLayoutAndWaitToBeActive()
// the resize is constrained by the parent's size
- requestSizeAndVerifyLayout(
+ requestResizeAndVerifyLayout(
/* requestedWidth=*/ mainLayoutWidth + 100,
/* requestedHeight=*/ mainLayoutHeight + 100,
/* expectedWidth=*/ mainLayoutWidth,
@@ -642,13 +642,13 @@
}
@Test
- fun requestSizeWithMeasureSpecExactly() {
+ fun requestResizeWithMeasureSpecExactly() {
view.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
addViewToLayoutAndWaitToBeActive()
val currentWidth = view.width
val currentHeight = view.height
// the request is a no-op when the MeasureSpec is EXACTLY
- requestSizeAndVerifyLayout(
+ requestResizeAndVerifyLayout(
/* requestedWidth=*/ currentWidth - 100,
/* requestedHeight=*/ currentHeight - 100,
/* expectedWidth=*/ currentWidth,
@@ -842,7 +842,7 @@
addViewToLayout(true, viewToAdd)
}
- private fun requestSizeAndVerifyLayout(
+ private fun requestResizeAndVerifyLayout(
requestedWidth: Int,
requestedHeight: Int,
expectedWidth: Int,
@@ -856,7 +856,7 @@
height = bottom - top
layoutLatch.countDown()
}
- activityScenarioRule.withActivity { view.requestSize(requestedWidth, requestedHeight) }
+ activityScenarioRule.withActivity { view.requestResize(requestedWidth, requestedHeight) }
assertThat(layoutLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
assertThat(width).isEqualTo(expectedWidth)
assertThat(height).isEqualTo(expectedHeight)
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/RemoteCallManager.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/RemoteCallManager.kt
new file mode 100644
index 0000000..6570440
--- /dev/null
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/RemoteCallManager.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 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.privacysandbox.ui.client
+
+import android.os.IBinder
+import android.os.RemoteException
+import android.util.Log
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ui.core.IRemoteSessionController
+
+/** Utility class for remote objects called by the UI library adapter factories. */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+object RemoteCallManager {
+
+ const val TAG = "PrivacySandboxUiLib"
+
+ fun addBinderDeathListener(
+ remoteSessionController: IRemoteSessionController,
+ recipient: IBinder.DeathRecipient
+ ) {
+ tryToCallRemoteObject(remoteSessionController) { this.asBinder().linkToDeath(recipient, 0) }
+ }
+
+ fun closeRemoteSession(remoteSessionController: IRemoteSessionController) {
+ tryToCallRemoteObject(remoteSessionController) { close() }
+ }
+
+ /** Tries to call the remote object and handles exceptions if the remote object has died. */
+ inline fun <RemoteObject> tryToCallRemoteObject(
+ remoteObject: RemoteObject,
+ function: RemoteObject.() -> Unit
+ ) {
+ try {
+ remoteObject.function()
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Calling remote object failed: $e")
+ }
+ }
+}
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
index 7504162..09be59b 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
@@ -23,7 +23,6 @@
import android.os.Build
import android.os.Bundle
import android.os.IBinder
-import android.os.RemoteException
import android.util.Log
import android.view.Display
import android.view.SurfaceControlViewHost
@@ -31,6 +30,9 @@
import android.view.View
import android.window.SurfaceSyncGroup
import androidx.annotation.RequiresApi
+import androidx.privacysandbox.ui.client.RemoteCallManager.addBinderDeathListener
+import androidx.privacysandbox.ui.client.RemoteCallManager.closeRemoteSession
+import androidx.privacysandbox.ui.client.RemoteCallManager.tryToCallRemoteObject
import androidx.privacysandbox.ui.core.IRemoteSessionClient
import androidx.privacysandbox.ui.core.IRemoteSessionController
import androidx.privacysandbox.ui.core.ISandboxedUiAdapter
@@ -259,8 +261,8 @@
context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val displayId = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).displayId
- tryToCallRemoteObject {
- adapterInterface.openRemoteSession(
+ tryToCallRemoteObject(adapterInterface) {
+ this.openRemoteSession(
windowInputToken,
displayId,
initialWidth,
@@ -299,8 +301,8 @@
override fun onViewAttachedToWindow(v: View) {
if (hasViewBeenPreviouslyAttached) {
- tryToCallRemoteObject {
- remoteSessionController.notifyFetchUiForSession()
+ tryToCallRemoteObject(remoteSessionController) {
+ this.notifyFetchUiForSession()
}
} else {
hasViewBeenPreviouslyAttached = true
@@ -321,10 +323,8 @@
)
)
}
- tryToCallRemoteObject {
- remoteSessionController
- .asBinder()
- .linkToDeath({ onRemoteSessionError("Remote process died") }, 0)
+ addBinderDeathListener(remoteSessionController) {
+ onRemoteSessionError("Remote process died")
}
}
@@ -361,8 +361,8 @@
}
override fun notifyConfigurationChanged(configuration: Configuration) {
- tryToCallRemoteObject {
- remoteSessionController.notifyConfigurationChanged(configuration)
+ tryToCallRemoteObject(remoteSessionController) {
+ this.notifyConfigurationChanged(configuration)
}
}
@@ -379,7 +379,9 @@
}
val providerResizeRunnable = Runnable {
- tryToCallRemoteObject { remoteSessionController.notifyResized(width, height) }
+ tryToCallRemoteObject(remoteSessionController) {
+ this.notifyResized(width, height)
+ }
}
val syncGroup = SurfaceSyncGroup("AppAndSdkViewsSurfaceSync")
@@ -391,29 +393,19 @@
override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
surfaceView.setZOrderOnTop(isZOrderOnTop)
- tryToCallRemoteObject { remoteSessionController.notifyZOrderChanged(isZOrderOnTop) }
+ tryToCallRemoteObject(remoteSessionController) {
+ this.notifyZOrderChanged(isZOrderOnTop)
+ }
}
override fun notifyUiChanged(uiContainerInfo: Bundle) {
- tryToCallRemoteObject { remoteSessionController.notifyUiChanged(uiContainerInfo) }
+ tryToCallRemoteObject(remoteSessionController) {
+ this.notifyUiChanged(uiContainerInfo)
+ }
}
override fun close() {
- tryToCallRemoteObject { remoteSessionController.close() }
- }
- }
-
- private companion object {
-
- /**
- * Tries to call the remote object and handles exceptions if the remote object has died.
- */
- private inline fun tryToCallRemoteObject(function: () -> Unit) {
- try {
- function()
- } catch (e: RemoteException) {
- Log.e(TAG, "Calling remote object failed: $e")
- }
+ closeRemoteSession(remoteSessionController)
}
}
}
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
index 6113489..a131ca8 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
@@ -226,7 +226,7 @@
}
}
- internal fun requestSize(width: Int, height: Int) {
+ internal fun requestResize(width: Int, height: Int) {
if (width == this.width && height == this.height) return
requestedWidth = width
requestedHeight = height
@@ -566,7 +566,7 @@
override fun onResizeRequested(width: Int, height: Int) {
if (sandboxedSdkView == null) return
- sandboxedSdkView?.requestSize(width, height)
+ sandboxedSdkView?.requestResize(width, height)
}
}
diff --git a/profileinstaller/integration-tests/profile-verification/build.gradle b/profileinstaller/integration-tests/profile-verification/build.gradle
index cf670e9..416e4a0 100644
--- a/profileinstaller/integration-tests/profile-verification/build.gradle
+++ b/profileinstaller/integration-tests/profile-verification/build.gradle
@@ -122,5 +122,5 @@
// It makes sure that the apks are generated before the assets are packed.
afterEvaluate {
- tasks.named("generateDebugAndroidTestAssets").configure { it.dependsOn(prepareAssetsTaskProvider) }
+ tasks.named("generateReleaseAndroidTestAssets").configure { it.dependsOn(prepareAssetsTaskProvider) }
}
diff --git a/remotecallback/remotecallback-processor/src/main/java/androidx/remotecallback/compiler/CallableMethod.java b/remotecallback/remotecallback-processor/src/main/java/androidx/remotecallback/compiler/CallableMethod.java
index 1f1c2b6..ed4e882 100644
--- a/remotecallback/remotecallback-processor/src/main/java/androidx/remotecallback/compiler/CallableMethod.java
+++ b/remotecallback/remotecallback-processor/src/main/java/androidx/remotecallback/compiler/CallableMethod.java
@@ -117,7 +117,7 @@
private AnnotationMirror findAnnotation(VariableElement element, String cls) {
for (AnnotationMirror mirror: element.getAnnotationMirrors()) {
- if (mirror.getAnnotationType().toString().equals(cls)) {
+ if (typeString(mirror.getAnnotationType()).equals(cls)) {
return mirror;
}
}
@@ -147,13 +147,13 @@
ProcessingEnvironment env, Messager messager) {
// Validate types
for (int i = 0; i < mTypes.size(); i++) {
- if (checkType(mTypes.get(i).toString(), messager)) {
+ if (checkType(typeString(mTypes.get(i)), messager)) {
messager.printMessage(Diagnostic.Kind.ERROR,
"Invalid type " + mTypes.get(i));
return;
}
}
- if (!"androidx.remotecallback.RemoteCallback".equals(mReturnType.toString())) {
+ if (!"androidx.remotecallback.RemoteCallback".equals(typeString(mReturnType))) {
messager.printMessage(Diagnostic.Kind.ERROR,
"RemoteCallable methods must return RemoteCallback.LOCAL.");
return;
@@ -183,20 +183,22 @@
methodCall.append(mElement.getSimpleName());
methodCall.append("(");
for (int i = 0; i < mNames.size(); i++) {
+ TypeMirror type = mTypes.get(i);
+ String typeString = typeString(type);
// Pass the parameter to the method call.
if (i != 0) {
methodCall.append(", ");
}
methodCall.append("p" + i);
- if (mTypes.get(i).toString().equals(context.toString())) {
+ if (typeString.equals(context.toString())) {
code.addStatement("$L p" + i + " = context", mTypes.get(i));
continue;
}
- code.addStatement("$L p" + i, mTypes.get(i));
+ code.addStatement("$L p" + i, type);
String key = mExtInputKeys.get(i) != null ? mExtInputKeys.get(i) : getBundleKey(i);
// Generate code to extract the value.
- code.addStatement("p$L = $L", i, getBundleParam(mTypes.get(i).toString(), key));
+ code.addStatement("p$L = $L", i, getBundleParam(typeString, key));
}
methodCall.append(")");
// Add the method call as the last thing.
@@ -218,18 +220,20 @@
code.addStatement("$L b = new $L()", bundle, bundle);
for (int i = 0; i < mNames.size(); i++) {
- builder.addParameter(TypeName.get(mTypes.get(i)), "p" + i);
- if (mTypes.get(i).toString().equals(context.toString())) {
+ TypeMirror type = mTypes.get(i);
+ String typeString = typeString(type);
+ builder.addParameter(TypeName.get(type), "p" + i);
+ if (typeString.equals(context.toString())) {
continue;
}
- boolean isNative = isNative(mTypes.get(i).toString());
+ boolean isNative = isNative(typeString);
// Only fill in value if the argument has a value.
if (!isNative) code.beginControlFlow("if (p$L != null)", i);
// Otherwise just need to place the arg value.
code.addStatement("b.put$L($L, ($L) p$L)",
- getTypeMethod(mTypes.get(i).toString()),
- getBundleKey(i), mTypes.get(i), i);
+ getTypeMethod(typeString),
+ getBundleKey(i), type, i);
// No value present, need an explicit null for security.
if (!isNative) code.nextControlFlow("else");
@@ -249,7 +253,7 @@
private int countArgs(ClassName context) {
int ct = 0;
for (int i = 0; i < mTypes.size(); i++) {
- if (mTypes.get(i).toString().equals(context.toString())) {
+ if (typeString(mTypes.get(i)).equals(context.toString())) {
continue;
}
ct++;
@@ -400,4 +404,9 @@
return true;
}
}
+
+ /** Returns a simple string version of the type, with no annotations. */
+ private String typeString(TypeMirror type) {
+ return TypeName.get(type).toString();
+ }
}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
index cf236fe..f9fbdbe 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
@@ -31,8 +31,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import java.util.concurrent.CopyOnWriteArrayList
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -82,11 +82,10 @@
@After
fun tearDown() {
database.close()
- testCoroutineScope.cancel()
}
@Test
- fun testInsert() {
+ fun testInsert() = runTest {
database
.queryInterceptorDao()
.insert(QueryInterceptorEntity("Insert", "Inserted a placeholder query"))
@@ -100,14 +99,14 @@
}
@Test
- fun testDelete() {
+ fun testDelete() = runTest {
database.queryInterceptorDao().delete("Insert")
assertQueryLogged("DELETE FROM queryInterceptorTestDatabase WHERE id=?", listOf("Insert"))
assertTransactionQueries()
}
@Test
- fun testUpdate() {
+ fun testUpdate() = runTest {
database
.queryInterceptorDao()
.insert(QueryInterceptorEntity("Insert", "Inserted a placeholder query"))
@@ -125,7 +124,7 @@
}
@Test
- fun testCompileStatement() {
+ fun testCompileStatement() = runTest {
assertEquals(queryAndArgs.size, 0)
database
.queryInterceptorDao()
@@ -137,7 +136,7 @@
}
@Test
- fun testLoggingSupportSQLiteQuery() {
+ fun testLoggingSupportSQLiteQuery() = runTest {
database.openHelper.writableDatabase.query(
SimpleSQLiteQuery(
"INSERT OR ABORT INTO `queryInterceptorTestDatabase` (`id`,`description`) " +
@@ -153,7 +152,7 @@
}
@Test
- fun testExecSQLWithBindArgs() {
+ fun testExecSQLWithBindArgs() = runTest {
database.openHelper.writableDatabase.execSQL(
"INSERT OR ABORT INTO `queryInterceptorTestDatabase` (`id`,`description`) " +
"VALUES (?,?)",
@@ -167,7 +166,7 @@
}
@Test
- fun testNullBindArgument() {
+ fun testNullBindArgument() = runTest {
database.openHelper.writableDatabase.query(
SimpleSQLiteQuery(
"INSERT OR ABORT INTO `queryInterceptorTestDatabase` (`id`,`description`) " +
@@ -183,7 +182,7 @@
}
@Test
- fun testNullBindArgumentCompileStatement() {
+ fun testNullBindArgumentCompileStatement() = runTest {
val sql =
"INSERT OR ABORT INTO `queryInterceptorTestDatabase` (`id`,`description`) " +
"VALUES (?,?)"
@@ -203,7 +202,7 @@
}
@Test
- fun testCallbackCalledOnceAfterCloseAndReOpen() {
+ fun testCallbackCalledOnceAfterCloseAndReOpen() = runTest {
val dbBuilder =
Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
@@ -218,8 +217,6 @@
dbBuilder.build().close()
- database = dbBuilder.build()
-
database
.queryInterceptorDao()
.insert(QueryInterceptorEntity("Insert", "Inserted a placeholder query"))
@@ -232,6 +229,12 @@
assertTransactionQueries()
}
+ private fun runTest(testBody: suspend TestScope.() -> Unit) =
+ testCoroutineScope.runTest {
+ testBody.invoke(this)
+ database.close()
+ }
+
private fun assertQueryLogged(query: String, expectedArgs: List<String?>) {
testCoroutineScope.testScheduler.advanceUntilIdle()
val filteredQueries = queryAndArgs.filter { it.first == query }
diff --git a/room/integration-tests/multiplatformtestapp/build.gradle b/room/integration-tests/multiplatformtestapp/build.gradle
index 95b2df0..4ba2f01 100644
--- a/room/integration-tests/multiplatformtestapp/build.gradle
+++ b/room/integration-tests/multiplatformtestapp/build.gradle
@@ -39,8 +39,10 @@
implementation(libs.kotlinStdlib)
implementation(project(":room:room-runtime"))
implementation(project(":room:room-testing"))
+ implementation(project(":room:room-paging"))
implementation(project(":sqlite:sqlite-bundled"))
implementation(project(":kruth:kruth"))
+ implementation(project(":paging:paging-common"))
implementation(libs.kotlinTest)
implementation(libs.kotlinCoroutinesTest)
}
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt
index 78e59dc..760c416 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseQueryTest.kt
@@ -18,6 +18,8 @@
import androidx.kruth.assertThat
import androidx.kruth.assertThrows
+import androidx.paging.PagingSource
+import androidx.paging.PagingSource.LoadResult
import androidx.room.RoomRawQuery
import androidx.room.execSQL
import androidx.room.immediateTransaction
@@ -503,4 +505,67 @@
.hasMessageThat()
.contains("Only bind*() calls are allowed")
}
+
+ @Test
+ fun simplePagingQuery() = runTest {
+ val entity1 = SampleEntity(1, 1)
+ val entity2 = SampleEntity(2, 2)
+ val sampleEntities = listOf(entity1, entity2)
+ val dao = db.dao()
+
+ dao.insertSampleEntityList(sampleEntities)
+ val pagingSource = dao.getAllIds()
+
+ val onlyLoadFirst =
+ pagingSource.load(
+ PagingSource.LoadParams.Refresh(
+ key = null,
+ loadSize = 1,
+ placeholdersEnabled = true
+ )
+ ) as LoadResult.Page
+ assertThat(onlyLoadFirst.data).containsExactly(entity1)
+
+ val loadAll =
+ pagingSource.load(
+ PagingSource.LoadParams.Refresh(
+ key = null,
+ loadSize = 2,
+ placeholdersEnabled = true
+ )
+ ) as LoadResult.Page
+ assertThat(loadAll.data).containsExactlyElementsIn(sampleEntities)
+ }
+
+ @Test
+ fun pagingQueryWithParams() = runTest {
+ val entity1 = SampleEntity(1, 1)
+ val entity2 = SampleEntity(2, 2)
+ val entity3 = SampleEntity(3, 3)
+ val sampleEntities = listOf(entity1, entity2, entity3)
+ val dao = db.dao()
+
+ dao.insertSampleEntityList(sampleEntities)
+ val pagingSource = dao.getAllIdsWithArgs(1)
+
+ val onlyLoadFirst =
+ pagingSource.load(
+ PagingSource.LoadParams.Refresh(
+ key = null,
+ loadSize = 1,
+ placeholdersEnabled = true
+ )
+ ) as LoadResult.Page
+ assertThat(onlyLoadFirst.data).containsExactly(entity2)
+
+ val loadAll =
+ pagingSource.load(
+ PagingSource.LoadParams.Refresh(
+ key = null,
+ loadSize = 2,
+ placeholdersEnabled = true
+ )
+ ) as LoadResult.Page
+ assertThat(loadAll.data).containsExactlyElementsIn(listOf(entity2, entity3))
+ }
}
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
index e511fc3..a2a3a6b 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
@@ -203,6 +203,12 @@
@Query("SELECT * FROM StringSampleEntity1")
suspend fun getSampleManyToMany(): SampleManyAndMany
+ @Query("SELECT * FROM SampleEntity")
+ fun getAllIds(): androidx.paging.PagingSource<Int, SampleEntity>
+
+ @Query("SELECT * FROM SampleEntity WHERE pk > :gt ORDER BY pk ASC")
+ fun getAllIdsWithArgs(gt: Long): androidx.paging.PagingSource<Int, SampleEntity>
+
data class Sample1And2(
@Embedded val sample1: SampleEntity,
@Relation(parentColumn = "pk", entityColumn = "pk2") val sample2: SampleEntity2
diff --git a/room/room-compiler/build.gradle b/room/room-compiler/build.gradle
index ff0e1d5..060f27d 100644
--- a/room/room-compiler/build.gradle
+++ b/room/room-compiler/build.gradle
@@ -23,6 +23,7 @@
*/
import androidx.build.BuildOnServerKt
+import androidx.build.KotlinTarget
import androidx.build.LibraryType
import androidx.build.SdkHelperKt
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@@ -240,4 +241,5 @@
type = LibraryType.ANNOTATION_PROCESSOR
inceptionYear = "2017"
description = "Android Room annotation processor"
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
index c73de92..832e27b 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
@@ -337,7 +337,8 @@
RxJava3TypeNames.COMPLETABLE,
GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE,
KotlinTypeNames.FLOW,
- ReactiveStreamsTypeNames.PUBLISHER
+ ReactiveStreamsTypeNames.PUBLISHER,
+ PagingTypeNames.PAGING_SOURCE
)
fun XTypeName.defaultValue(): String {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
index edd1307..384b238 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
@@ -21,6 +21,7 @@
import androidx.room.compiler.processing.XRawType
import androidx.room.compiler.processing.XType
import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.PagingTypeNames
import androidx.room.parser.ParsedQuery
import androidx.room.processor.Context
import androidx.room.processor.ProcessorErrors
@@ -33,7 +34,7 @@
class MultiTypedPagingSourceQueryResultBinderProvider(
private val context: Context,
private val roomPagingClassName: XClassName,
- pagingSourceTypeName: XClassName,
+ private val pagingSourceTypeName: XClassName,
) : QueryResultBinderProvider {
private val pagingSourceType: XRawType? by lazy {
@@ -60,7 +61,8 @@
return MultiTypedPagingSourceQueryResultBinder(
listAdapter = listAdapter,
tableNames = tableNames,
- className = roomPagingClassName
+ className = roomPagingClassName,
+ isBasePagingSource = pagingSourceTypeName == PagingTypeNames.PAGING_SOURCE
)
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
index 41c114e..8eea718 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
@@ -91,7 +91,7 @@
override fun convertAndReturn(
sqlQueryVar: String,
dbProperty: XPropertySpec,
- bindStatement: CodeGenScope.(String) -> Unit,
+ bindStatement: (CodeGenScope.(String) -> Unit)?,
returnTypeName: XTypeName,
inTransaction: Boolean,
scope: CodeGenScope
@@ -128,7 +128,7 @@
sqlQueryVar
)
beginControlFlow("try")
- bindStatement(scope, statementVar)
+ bindStatement?.invoke(scope, statementVar)
val outVar = scope.getTmpVar("_result")
adapter?.convert(outVar, statementVar, scope)
addStatement("$returnPrefix%L", outVar)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
index 2a82c10..e9ba2ce 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
@@ -158,7 +158,7 @@
override fun convertAndReturn(
sqlQueryVar: String,
dbProperty: XPropertySpec,
- bindStatement: CodeGenScope.(String) -> Unit,
+ bindStatement: (CodeGenScope.(String) -> Unit)?,
returnTypeName: XTypeName,
inTransaction: Boolean,
scope: CodeGenScope
@@ -194,7 +194,7 @@
sqlQueryVar
)
beginControlFlow("try")
- bindStatement(scope, statementVar)
+ bindStatement?.invoke(scope, statementVar)
val outVar = scope.getTmpVar("_result")
adapter?.convert(outVar, statementVar, scope)
addStatement("$returnPrefix%L", outVar)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
index 647d801..26826b7 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
@@ -95,7 +95,7 @@
override fun convertAndReturn(
sqlQueryVar: String,
dbProperty: XPropertySpec,
- bindStatement: CodeGenScope.(String) -> Unit,
+ bindStatement: (CodeGenScope.(String) -> Unit)?,
returnTypeName: XTypeName,
inTransaction: Boolean,
scope: CodeGenScope
@@ -130,7 +130,7 @@
sqlQueryVar
)
beginControlFlow("try")
- bindStatement(scope, statementVar)
+ bindStatement?.invoke(scope, statementVar)
val outVar = scope.getTmpVar("_result")
adapter?.convert(outVar, statementVar, scope)
addStatement("$returnPrefix%L", outVar)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
index 5f1e385..9205913 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
@@ -85,7 +85,7 @@
override fun convertAndReturn(
sqlQueryVar: String,
dbProperty: XPropertySpec,
- bindStatement: CodeGenScope.(String) -> Unit,
+ bindStatement: (CodeGenScope.(String) -> Unit)?,
returnTypeName: XTypeName,
inTransaction: Boolean,
scope: CodeGenScope
@@ -120,7 +120,7 @@
sqlQueryVar
)
beginControlFlow("try")
- bindStatement(scope, statementVar)
+ bindStatement?.invoke(scope, statementVar)
val outVar = scope.getTmpVar("_result")
adapter?.convert(outVar, statementVar, scope)
addStatement("$returnPrefix%L", outVar)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
index e4898ee..683be46 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
@@ -93,7 +93,7 @@
override fun convertAndReturn(
sqlQueryVar: String,
dbProperty: XPropertySpec,
- bindStatement: CodeGenScope.(String) -> Unit,
+ bindStatement: (CodeGenScope.(String) -> Unit)?,
returnTypeName: XTypeName,
inTransaction: Boolean,
scope: CodeGenScope
@@ -139,7 +139,7 @@
sqlQueryVar
)
beginControlFlow("try")
- bindStatement(scope, statementVar)
+ bindStatement?.invoke(scope, statementVar)
val outVar = scope.getTmpVar("_result")
adapter?.convert(outVar, statementVar, scope)
addStatement("$returnPrefix%L", outVar)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultiTypedPagingSourceQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultiTypedPagingSourceQueryResultBinder.kt
index 8b3f9a2..055401a 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultiTypedPagingSourceQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultiTypedPagingSourceQueryResultBinder.kt
@@ -16,15 +16,21 @@
package androidx.room.solver.query.result
+import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.VisibilityModifier
import androidx.room.compiler.codegen.XClassName
+import androidx.room.compiler.codegen.XCodeBlock
import androidx.room.compiler.codegen.XFunSpec
import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
import androidx.room.compiler.codegen.XPropertySpec
import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.ext.AndroidTypeNames.CURSOR
+import androidx.room.ext.AndroidTypeNames
import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.RoomTypeNames.RAW_QUERY
+import androidx.room.ext.SQLiteDriverTypeNames
import androidx.room.solver.CodeGenScope
/**
@@ -35,13 +41,16 @@
class MultiTypedPagingSourceQueryResultBinder(
private val listAdapter: ListQueryResultAdapter?,
private val tableNames: Set<String>,
- className: XClassName
+ className: XClassName,
+ val isBasePagingSource: Boolean
) : QueryResultBinder(listAdapter) {
private val itemTypeName: XTypeName =
listAdapter?.rowAdapters?.firstOrNull()?.out?.asTypeName() ?: XTypeName.ANY_OBJECT
private val pagingSourceTypeName: XTypeName = className.parametrizedBy(itemTypeName)
+ override fun isMigratedToDriver(): Boolean = isBasePagingSource
+
override fun convertAndReturn(
roomSQLiteQueryVar: String,
canReleaseQuery: Boolean,
@@ -61,14 +70,134 @@
)
.apply {
superclass(pagingSourceTypeName)
- addFunction(createConvertRowsMethod(scope))
+ addFunction(
+ createConvertRowsMethod(
+ scope = scope,
+ stmtParamName = "cursor",
+ stmtParamTypeName = AndroidTypeNames.CURSOR,
+ rawQueryParamName = null
+ )
+ )
}
.build()
addStatement("return %L", pagingSourceSpec)
}
}
- private fun createConvertRowsMethod(scope: CodeGenScope): XFunSpec {
+ override fun convertAndReturn(
+ sqlQueryVar: String,
+ dbProperty: XPropertySpec,
+ bindStatement: (CodeGenScope.(String) -> Unit)?,
+ returnTypeName: XTypeName,
+ inTransaction: Boolean,
+ scope: CodeGenScope
+ ) {
+ check(isBasePagingSource) {
+ "This version of `convertAndReturn` should only be called when the binder is for the " +
+ "base PagingSource. "
+ }
+ val rawQueryVarName = scope.getTmpVar("_rawQuery")
+ val stmtVarName = scope.getTmpVar("_stmt")
+
+ when (scope.language) {
+ CodeLanguage.JAVA -> {
+ val assignExpr =
+ if (bindStatement != null) {
+ XCodeBlock.ofNewInstance(
+ language = scope.language,
+ typeName = RAW_QUERY,
+ "%L, %L",
+ sqlQueryVar,
+ Function1TypeSpec(
+ language = scope.language,
+ parameterTypeName = SQLiteDriverTypeNames.STATEMENT,
+ parameterName = stmtVarName,
+ returnTypeName = KotlinTypeNames.UNIT
+ ) {
+ val functionScope = scope.fork()
+ functionScope.builder
+ .apply { bindStatement.invoke(functionScope, stmtVarName) }
+ .build()
+ addCode(functionScope.generate())
+ addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
+ }
+ )
+ } else {
+ XCodeBlock.ofNewInstance(
+ language = scope.language,
+ typeName = RAW_QUERY,
+ "%L",
+ sqlQueryVar
+ )
+ }
+ scope.builder.addLocalVariable(
+ name = rawQueryVarName,
+ typeName = RAW_QUERY,
+ assignExpr = assignExpr
+ )
+ }
+ CodeLanguage.KOTLIN ->
+ scope.builder.apply {
+ if (bindStatement != null) {
+ beginControlFlow(
+ "val %L: %T = %T(%N) { %L ->",
+ rawQueryVarName,
+ RAW_QUERY,
+ RAW_QUERY,
+ sqlQueryVar,
+ stmtVarName
+ )
+ bindStatement.invoke(scope, stmtVarName)
+ endControlFlow()
+ } else {
+ addLocalVariable(
+ name = rawQueryVarName,
+ typeName = RAW_QUERY,
+ assignExpr =
+ XCodeBlock.ofNewInstance(
+ language = scope.language,
+ typeName = RAW_QUERY,
+ argsFormat = "%N",
+ sqlQueryVar
+ )
+ )
+ }
+ }
+ }
+
+ scope.builder.apply {
+ val tableNamesList = tableNames.joinToString(", ") { "\"$it\"" }
+ val statementParamName = "statement"
+ val pagingSourceSpec =
+ XTypeSpec.anonymousClassBuilder(
+ language = language,
+ argsFormat = "%L, %N, %L",
+ rawQueryVarName,
+ dbProperty,
+ tableNamesList
+ )
+ .apply {
+ superclass(pagingSourceTypeName)
+ addFunction(
+ createConvertRowsMethod(
+ scope = scope,
+ stmtParamName = statementParamName,
+ stmtParamTypeName = SQLiteDriverTypeNames.STATEMENT,
+ rawQueryParamName = rawQueryVarName
+ )
+ )
+ }
+ .build()
+ addStatement("return %L", pagingSourceSpec)
+ }
+ }
+
+ private fun createConvertRowsMethod(
+ scope: CodeGenScope,
+ stmtParamName: String,
+ stmtParamTypeName: XTypeName,
+ rawQueryParamName: String?
+ ): XFunSpec {
return XFunSpec.builder(
language = scope.language,
name = "convertRows",
@@ -76,12 +205,24 @@
isOverride = true
)
.apply {
- val cursorParamName = "cursor"
returns(CommonTypeNames.LIST.parametrizedBy(itemTypeName))
- addParameter(typeName = CURSOR, name = cursorParamName)
+ addParameter(typeName = stmtParamTypeName, name = stmtParamName)
+ if (stmtParamTypeName == SQLiteDriverTypeNames.STATEMENT) {
+ // The SQLiteStatement version requires a second parameter for backwards
+ // compatibility for delegating to CursorSQLiteStatement.
+ addParameter(typeName = XTypeName.PRIMITIVE_INT, name = "itemCount")
+ }
val resultVar = scope.getTmpVar("_result")
val rowsScope = scope.fork()
- listAdapter?.convert(resultVar, cursorParamName, rowsScope)
+ if (stmtParamTypeName == SQLiteDriverTypeNames.STATEMENT) {
+ checkNotNull(rawQueryParamName)
+ addStatement(
+ "%L.getBindingFunction().invoke(%L)",
+ rawQueryParamName,
+ stmtParamName,
+ )
+ }
+ listAdapter?.convert(resultVar, stmtParamName, rowsScope)
addCode(rowsScope.generate())
addStatement("return %L", resultVar)
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt
index b39c993..286394d 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt
@@ -51,7 +51,7 @@
open fun convertAndReturn(
sqlQueryVar: String,
dbProperty: XPropertySpec,
- bindStatement: CodeGenScope.(String) -> Unit,
+ bindStatement: (CodeGenScope.(String) -> Unit)?,
returnTypeName: XTypeName,
inTransaction: Boolean,
scope: CodeGenScope
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxLambdaQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxLambdaQueryResultBinder.kt
index 10973f3..57a43cb 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxLambdaQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxLambdaQueryResultBinder.kt
@@ -171,7 +171,7 @@
override fun convertAndReturn(
sqlQueryVar: String,
dbProperty: XPropertySpec,
- bindStatement: CodeGenScope.(String) -> Unit,
+ bindStatement: (CodeGenScope.(String) -> Unit)?,
returnTypeName: XTypeName,
inTransaction: Boolean,
scope: CodeGenScope
@@ -206,7 +206,7 @@
sqlQueryVar
)
beginControlFlow("try")
- bindStatement(scope, statementVar)
+ bindStatement?.invoke(scope, statementVar)
val outVar = scope.getTmpVar("_result")
adapter?.convert(outVar, statementVar, scope)
addStatement("$returnPrefix%L", outVar)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
index bc8877e..716fc2c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
@@ -90,7 +90,7 @@
override fun convertAndReturn(
sqlQueryVar: String,
dbProperty: XPropertySpec,
- bindStatement: CodeGenScope.(String) -> Unit,
+ bindStatement: (CodeGenScope.(String) -> Unit)?,
returnTypeName: XTypeName,
inTransaction: Boolean,
scope: CodeGenScope
@@ -134,7 +134,7 @@
sqlQueryVar
)
beginControlFlow("try")
- bindStatement(scope, statementVar)
+ bindStatement?.invoke(scope, statementVar)
val outVar = scope.getTmpVar("_result")
adapter?.convert(outVar, statementVar, scope)
addStatement("$returnPrefix%L", outVar)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
index 8ff5d2d..45a0481 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -685,7 +685,12 @@
method.queryResultBinder.convertAndReturn(
sqlQueryVar = sqlVar,
dbProperty = dbProperty,
- bindStatement = { stmtVar -> queryWriter.bindArgs(stmtVar, listSizeArgs, this) },
+ bindStatement =
+ if (queryWriter.parameters.isNotEmpty()) {
+ { stmtVar -> queryWriter.bindArgs(stmtVar, listSizeArgs, this) }
+ } else {
+ null
+ },
returnTypeName = method.returnType.asTypeName(),
inTransaction = method.inTransaction,
scope = scope
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
index a4afa61..63d5be5 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
@@ -784,6 +784,9 @@
@Query("SELECT pk FROM MyEntity")
abstract fun getAllIds(): androidx.paging.PagingSource<Int, MyEntity>
+ @Query("SELECT * FROM MyEntity WHERE pk > :gt ORDER BY pk ASC")
+ abstract fun getAllIdsWithArgs(gt: Long): androidx.paging.PagingSource<Int, MyEntity>
+
@Query("SELECT pk FROM MyEntity")
abstract fun getAllIdsRx2(): androidx.paging.rxjava2.RxPagingSource<Int, MyEntity>
@@ -1041,7 +1044,6 @@
"MyDao.kt",
"""
import androidx.room.*
- import androidx.sqlite.db.SupportSQLiteQuery
@Dao
interface MyDao {
@@ -1075,7 +1077,6 @@
"MyDao.kt",
"""
import androidx.room.*
- import androidx.sqlite.db.SupportSQLiteQuery
interface BaseDao<T> {
fun getEntity(id: T): MyEntity
@@ -1117,7 +1118,6 @@
"MyDao.kt",
"""
import androidx.room.*
- import androidx.sqlite.db.SupportSQLiteQuery
interface BaseDao {
@Transaction
@@ -1186,7 +1186,6 @@
"MyDao.kt",
"""
import androidx.room.*
- import androidx.sqlite.db.SupportSQLiteQuery
interface BaseDao {
@Transaction
diff --git a/room/room-compiler/src/test/test-data/common/input/LimitOffsetPagingSource.kt b/room/room-compiler/src/test/test-data/common/input/LimitOffsetPagingSource.kt
index 7b72548..a9921a2 100644
--- a/room/room-compiler/src/test/test-data/common/input/LimitOffsetPagingSource.kt
+++ b/room/room-compiler/src/test/test-data/common/input/LimitOffsetPagingSource.kt
@@ -15,14 +15,14 @@
*/
package androidx.room.paging
-import android.database.Cursor
import androidx.paging.PagingState
import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
+import androidx.room.RoomRawQuery
+import androidx.sqlite.SQLiteStatement
@Suppress("UNUSED_PARAMETER")
abstract class LimitOffsetPagingSource<T : Any>(
- private val sourceQuery: RoomSQLiteQuery,
+ private val sourceQuery: RoomRawQuery,
private val db: RoomDatabase,
vararg tables: String
) : androidx.paging.PagingSource<Int, T>() {
@@ -33,5 +33,5 @@
override public suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
return LoadResult.Invalid()
}
- protected abstract fun convertRows(cursor: Cursor): List<T>
+ protected abstract fun convertRows(statement: SQLiteStatement, itemCount: Int): List<T>
}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
index 386d206..eab9d14 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
@@ -2,12 +2,16 @@
import androidx.paging.ListenableFuturePagingSource
import androidx.paging.PagingSource
import androidx.room.RoomDatabase
+import androidx.room.RoomRawQuery
import androidx.room.RoomSQLiteQuery
import androidx.room.RoomSQLiteQuery.Companion.acquire
import androidx.room.paging.LimitOffsetPagingSource
import androidx.room.paging.guava.LimitOffsetListenableFuturePagingSource
+import androidx.room.util.getColumnIndexOrThrow
+import androidx.sqlite.SQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
+import kotlin.Long
import kotlin.String
import kotlin.Suppress
import kotlin.collections.List
@@ -31,15 +35,41 @@
public override fun getAllIds(): PagingSource<Int, MyEntity> {
val _sql: String = "SELECT pk FROM MyEntity"
- val _statement: RoomSQLiteQuery = acquire(_sql, 0)
- return object : LimitOffsetPagingSource<MyEntity>(_statement, __db, "MyEntity") {
- protected override fun convertRows(cursor: Cursor): List<MyEntity> {
+ val _rawQuery: RoomRawQuery = RoomRawQuery(_sql)
+ return object : LimitOffsetPagingSource<MyEntity>(_rawQuery, __db, "MyEntity") {
+ protected override fun convertRows(statement: SQLiteStatement, itemCount: Int):
+ List<MyEntity> {
+ _rawQuery.getBindingFunction().invoke(statement)
val _cursorIndexOfPk: Int = 0
val _result: MutableList<MyEntity> = mutableListOf()
- while (cursor.moveToNext()) {
+ while (statement.step()) {
val _item: MyEntity
val _tmpPk: Int
- _tmpPk = cursor.getInt(_cursorIndexOfPk)
+ _tmpPk = statement.getLong(_cursorIndexOfPk).toInt()
+ _item = MyEntity(_tmpPk)
+ _result.add(_item)
+ }
+ return _result
+ }
+ }
+ }
+
+ public override fun getAllIdsWithArgs(gt: Long): PagingSource<Int, MyEntity> {
+ val _sql: String = "SELECT * FROM MyEntity WHERE pk > ? ORDER BY pk ASC"
+ val _rawQuery: RoomRawQuery = RoomRawQuery(_sql) { _stmt ->
+ var _argIndex: Int = 1
+ _stmt.bindLong(_argIndex, gt)
+ }
+ return object : LimitOffsetPagingSource<MyEntity>(_rawQuery, __db, "MyEntity") {
+ protected override fun convertRows(statement: SQLiteStatement, itemCount: Int):
+ List<MyEntity> {
+ _rawQuery.getBindingFunction().invoke(statement)
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(statement, "pk")
+ val _result: MutableList<MyEntity> = mutableListOf()
+ while (statement.step()) {
+ val _item: MyEntity
+ val _tmpPk: Int
+ _tmpPk = statement.getLong(_cursorIndexOfPk).toInt()
_item = MyEntity(_tmpPk)
_result.add(_item)
}
diff --git a/room/room-migration/build.gradle b/room/room-migration/build.gradle
index b10beda..8051a15 100644
--- a/room/room-migration/build.gradle
+++ b/room/room-migration/build.gradle
@@ -22,6 +22,8 @@
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.PlatformIdentifier
import androidx.build.LibraryType
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -88,4 +90,5 @@
description = "Android Room Migration"
legacyDisableKotlinStrictApiMode = true
metalavaK2UastEnabled = false
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/room/room-paging/build.gradle b/room/room-paging/build.gradle
index 8770d1d..c84fe70 100644
--- a/room/room-paging/build.gradle
+++ b/room/room-paging/build.gradle
@@ -17,7 +17,6 @@
import androidx.build.PlatformIdentifier
import androidx.build.LibraryType
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.konan.target.Family
plugins {
id("AndroidXPlugin")
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTest.kt
index bd36947..f525bd5 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTest.kt
@@ -27,8 +27,8 @@
import androidx.testutils.assertThrows
import java.io.IOException
import java.util.concurrent.TimeUnit
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -87,14 +87,12 @@
@After
fun cleanUp() {
- testWatch.step()
// At the end of all tests we always expect to auto-close the database
assertWithMessage("Database was not closed").that(autoCloser.delegateDatabase).isNull()
- testCoroutineScope.cancel()
}
@Test
- fun refCountsCounted() {
+ fun refCountsCounted() = runTest {
autoCloser.incrementCountAndEnsureDbIsOpen()
assertThat(autoCloser.refCountForTest).isEqualTo(1)
@@ -114,14 +112,14 @@
}
@Test
- fun executeRefCountingFunctionPropagatesFailure() {
+ fun executeRefCountingFunctionPropagatesFailure() = runTest {
assertThrows<IOException> { autoCloser.executeRefCountingFunction { throw IOException() } }
assertThat(autoCloser.refCountForTest).isEqualTo(0)
}
@Test
- fun dbNotClosedWithRefCountIncremented() {
+ fun dbNotClosedWithRefCountIncremented() = runTest {
autoCloser.incrementCountAndEnsureDbIsOpen()
testWatch.step()
@@ -132,7 +130,7 @@
}
@Test
- fun getDelegatedDatabaseReturnsUnwrappedDatabase() {
+ fun getDelegatedDatabaseReturnsUnwrappedDatabase() = runTest {
assertThat(autoCloser.delegateDatabase).isNull()
val db = autoCloser.incrementCountAndEnsureDbIsOpen()
@@ -152,7 +150,7 @@
}
@Test
- fun refCountStaysIncrementedWhenErrorIsEncountered() {
+ fun refCountStaysIncrementedWhenErrorIsEncountered() = runTest {
callback.throwOnOpen = true
assertThrows<IOException> { autoCloser.incrementCountAndEnsureDbIsOpen() }
@@ -163,7 +161,7 @@
}
@Test
- fun testDbCanBeManuallyClosed() {
+ fun testDbCanBeManuallyClosed() = runTest {
val db = autoCloser.incrementCountAndEnsureDbIsOpen()
assertThat(db.isOpen).isTrue()
@@ -180,4 +178,10 @@
assertThrows<IllegalStateException> { autoCloser.incrementCountAndEnsureDbIsOpen() }
}
+
+ private fun runTest(testBody: suspend TestScope.() -> Unit) =
+ testCoroutineScope.runTest {
+ testBody.invoke(this)
+ testWatch.step()
+ }
}
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactoryTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactoryTest.kt
index 7bb7cd1..373abbf 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactoryTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactoryTest.kt
@@ -25,8 +25,8 @@
import androidx.test.core.app.ApplicationProvider
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -65,14 +65,12 @@
@After
fun cleanUp() {
- testWatch.step()
// At the end of all tests we always expect to auto-close the database
assertWithMessage("Database was not closed").that(autoCloser.delegateDatabase).isNull()
- testCoroutineScope.cancel()
}
@Test
- fun testCallbacksCalled() {
+ fun testCallbacksCalled() = runTest {
val callbackCount = AtomicInteger()
val countingCallback =
@@ -121,7 +119,7 @@
}
@Test
- fun testDatabaseIsOpenForSlowCallbacks() {
+ fun testDatabaseIsOpenForSlowCallbacks() = runTest {
val refCountCheckingCallback =
object : SupportSQLiteOpenHelper.Callback(1) {
@SuppressLint("BanThreadSleep")
@@ -162,4 +160,10 @@
val db = autoClosingRoomOpenHelper.writableDatabase
assertTrue(db.isOpen)
}
+
+ private fun runTest(testBody: suspend TestScope.() -> Unit) =
+ testCoroutineScope.runTest {
+ testBody.invoke(this)
+ testWatch.step()
+ }
}
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperTest.kt
index 4781635..0f0474c 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperTest.kt
@@ -28,8 +28,8 @@
import androidx.testutils.assertThrows
import java.io.IOException
import java.util.concurrent.TimeUnit
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -89,14 +89,12 @@
@After
fun cleanUp() {
- testWatch.step()
// At the end of all tests we always expect to auto-close the database
assertWithMessage("Database was not closed").that(autoCloser.delegateDatabase).isNull()
- testCoroutineScope.cancel()
}
@Test
- fun testQueryFailureDecrementsRefCount() {
+ fun testQueryFailureDecrementsRefCount() = runTest {
assertThrows<SQLiteException> {
autoClosingRoomOpenHelper.writableDatabase.query("select * from nonexistanttable")
}
@@ -105,7 +103,7 @@
}
@Test
- fun testCursorKeepsDbAlive() {
+ fun testCursorKeepsDbAlive() = runTest {
autoClosingRoomOpenHelper.writableDatabase.execSQL("create table user (idk int)")
val cursor = autoClosingRoomOpenHelper.writableDatabase.query("select * from user")
@@ -115,7 +113,7 @@
}
@Test
- fun testTransactionKeepsDbAlive() {
+ fun testTransactionKeepsDbAlive() = runTest {
autoClosingRoomOpenHelper.writableDatabase.beginTransaction()
assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(1)
autoClosingRoomOpenHelper.writableDatabase.endTransaction()
@@ -123,7 +121,7 @@
}
@Test
- fun enableWriteAheadLogging_onOpenHelper() {
+ fun enableWriteAheadLogging_onOpenHelper() = runTest {
autoClosingRoomOpenHelper.setWriteAheadLoggingEnabled(true)
assertThat(autoClosingRoomOpenHelper.writableDatabase.isWriteAheadLoggingEnabled).isTrue()
@@ -133,7 +131,7 @@
}
@Test
- fun testEnableWriteAheadLogging_onSupportSqliteDatabase_throwsUnsupportedOperation() {
+ fun testEnableWriteAheadLogging_onSupportSqliteDatabase_throwsUnsupportedOperation() = runTest {
assertThrows<UnsupportedOperationException> {
autoClosingRoomOpenHelper.writableDatabase.enableWriteAheadLogging()
}
@@ -144,7 +142,7 @@
}
@Test
- fun testStatementReturnedByCompileStatement_doesNotKeepDatabaseOpen() {
+ fun testStatementReturnedByCompileStatement_doesNotKeepDatabaseOpen() = runTest {
val db = autoClosingRoomOpenHelper.writableDatabase
db.execSQL("create table user (idk int)")
@@ -157,7 +155,7 @@
}
@Test
- fun testStatementReturnedByCompileStatement_reOpensDatabase() {
+ fun testStatementReturnedByCompileStatement_reOpensDatabase() = runTest {
val db = autoClosingRoomOpenHelper.writableDatabase
db.execSQL("create table user (idk int)")
@@ -173,7 +171,7 @@
}
@Test
- fun testStatementReturnedByCompileStatement_worksWithBinds() {
+ fun testStatementReturnedByCompileStatement_worksWithBinds() = runTest {
val db = autoClosingRoomOpenHelper.writableDatabase
db.execSQL("create table users (i int, d double, b blob, n int, s string)")
@@ -213,7 +211,7 @@
}
@Test
- fun testGetDelegate() {
+ fun testGetDelegate() = runTest {
val delegateOpenHelper =
FrameworkSQLiteOpenHelperFactory()
.create(
@@ -236,4 +234,10 @@
assertThat(autoClosing.delegate).isSameInstanceAs(delegateOpenHelper)
}
+
+ private fun runTest(testBody: suspend TestScope.() -> Unit) =
+ testCoroutineScope.runTest {
+ testBody.invoke(this)
+ testWatch.step()
+ }
}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt
index 1673b96..20995b3 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt
@@ -36,13 +36,12 @@
inTransaction: Boolean,
callableFunction: Callable<T?>
): LiveData<T> {
- return RoomTrackingLiveData(
+ return RoomCallableTrackingLiveData(
database = database,
container = this,
inTransaction = inTransaction,
- callableFunction = callableFunction,
- lambdaFunction = null,
- tableNames = tableNames
+ tableNames = tableNames,
+ callableFunction = callableFunction
)
}
@@ -51,13 +50,12 @@
inTransaction: Boolean,
lambdaFunction: (SQLiteConnection) -> T?
): LiveData<T> {
- return RoomTrackingLiveData(
+ return RoomLambdaTrackingLiveData(
database = database,
container = this,
inTransaction = inTransaction,
- callableFunction = null,
- lambdaFunction = lambdaFunction,
- tableNames = tableNames
+ tableNames = tableNames,
+ lambdaFunction = lambdaFunction
)
}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
index 2cd882f..b63a06e 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
@@ -549,9 +549,6 @@
* Once a [RoomDatabase] is closed it should no longer be used.
*/
actual open fun close() {
- if (inCompatibilityMode() && !isOpen) {
- return
- }
closeBarrier.close()
}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt
index 9450a2b..5115d6f 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt
@@ -15,6 +15,8 @@
*/
package androidx.room
+import androidx.annotation.MainThread
+import androidx.arch.core.executor.ArchTaskExecutor
import androidx.lifecycle.LiveData
import androidx.room.util.performSuspending
import androidx.sqlite.SQLiteConnection
@@ -35,18 +37,16 @@
* This [LiveData] keeps a weak observer to the [InvalidationTracker] but it is hold strongly by the
* [InvalidationTracker] as long as it is active.
*/
-internal class RoomTrackingLiveData<T>(
- private val database: RoomDatabase,
+internal sealed class RoomTrackingLiveData<T>(
+ protected val database: RoomDatabase,
private val container: InvalidationLiveDataContainer,
- private val inTransaction: Boolean,
- private val callableFunction: Callable<T?>?,
- private val lambdaFunction: ((SQLiteConnection) -> T?)?,
+ protected val inTransaction: Boolean,
tableNames: Array<out String>
) : LiveData<T>() {
private val observer: InvalidationTracker.Observer =
object : InvalidationTracker.Observer(tableNames) {
override fun onInvalidated(tables: Set<String>) {
- database.getCoroutineScope().launch { invalidated() }
+ ArchTaskExecutor.getInstance().executeOnMainThread { invalidated() }
}
}
private val invalid = AtomicBoolean(true)
@@ -74,22 +74,7 @@
while (invalid.compareAndSet(true, false)) {
computed = true
try {
- value =
- if (callableFunction != null) {
- withContext(
- if (inTransaction) {
- database.getTransactionContext()
- } else {
- database.getQueryContext()
- }
- ) {
- callableFunction.call()
- }
- } else if (lambdaFunction != null) {
- performSuspending(database, true, inTransaction, lambdaFunction)
- } else {
- error("Both callable and lambda functions are null")
- }
+ value = compute()
} catch (e: Exception) {
throw RuntimeException(
"Exception while computing database live data.",
@@ -115,15 +100,18 @@
} while (computed && invalid.get())
}
- private suspend fun invalidated() {
+ @MainThread
+ private fun invalidated() {
val isActive = hasActiveObservers()
if (invalid.compareAndSet(false, true)) {
if (isActive) {
- refresh()
+ database.getCoroutineScope().launch { refresh() }
}
}
}
+ abstract suspend fun compute(): T?
+
override fun onActive() {
super.onActive()
container.onActive(this)
@@ -135,3 +123,33 @@
container.onInactive(this)
}
}
+
+internal class RoomCallableTrackingLiveData<T>(
+ database: RoomDatabase,
+ container: InvalidationLiveDataContainer,
+ inTransaction: Boolean,
+ tableNames: Array<out String>,
+ private val callableFunction: Callable<T?>
+) : RoomTrackingLiveData<T>(database, container, inTransaction, tableNames) {
+ override suspend fun compute(): T? {
+ val queryContext =
+ if (inTransaction) {
+ database.getTransactionContext()
+ } else {
+ database.getQueryContext()
+ }
+ return withContext(queryContext) { callableFunction.call() }
+ }
+}
+
+internal class RoomLambdaTrackingLiveData<T>(
+ database: RoomDatabase,
+ container: InvalidationLiveDataContainer,
+ inTransaction: Boolean,
+ tableNames: Array<out String>,
+ private val lambdaFunction: ((SQLiteConnection) -> T?)
+) : RoomTrackingLiveData<T>(database, container, inTransaction, tableNames) {
+ override suspend fun compute(): T? {
+ return performSuspending(database, true, inTransaction, lambdaFunction)
+ }
+}
diff --git a/room/room-testing/build.gradle b/room/room-testing/build.gradle
index 13685a4..14a4823 100644
--- a/room/room-testing/build.gradle
+++ b/room/room-testing/build.gradle
@@ -22,6 +22,8 @@
* modifying its settings.
*/
+
+import androidx.build.KotlinTarget
import androidx.build.PlatformIdentifier
import androidx.build.LibraryType
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -97,4 +99,5 @@
description = "Android Room Testing"
legacyDisableKotlinStrictApiMode = true
metalavaK2UastEnabled = false
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/settings.gradle b/settings.gradle
index c02141b..ded0605 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -26,7 +26,7 @@
dependencies {
// upgrade protobuf to be compatible with AGP
classpath("com.google.protobuf:protobuf-java:3.22.3")
- classpath("com.gradle:develocity-gradle-plugin:3.17.2")
+ classpath("com.gradle:develocity-gradle-plugin:3.18")
classpath("com.gradle:common-custom-user-data-gradle-plugin:2.0.1")
classpath("androidx.build.gradle.gcpbuildcache:gcpbuildcache:1.0.0-beta10")
classpath("com.android.settings:com.android.settings.gradle.plugin:8.7.0-alpha02")
@@ -419,6 +419,8 @@
includeProject(":camera:camera-camera2-pipe", [BuildType.CAMERA])
includeProject(":camera:camera-camera2-pipe-integration", [BuildType.CAMERA])
includeProject(":camera:camera-camera2-pipe-testing", [BuildType.CAMERA])
+includeProject(":camera:camera-compose", [BuildType.CAMERA])
+includeProject(":camera:camera-compose:camera-compose-samples", "camera/camera-compose/samples", [BuildType.CAMERA])
includeProject(":camera:camera-core", [BuildType.CAMERA])
includeProject(":camera:camera-effects", [BuildType.CAMERA])
includeProject(":camera:camera-effects-still-portrait", [BuildType.CAMERA])
@@ -506,6 +508,7 @@
includeProject(":compose:material3:adaptive:adaptive", [BuildType.COMPOSE])
includeProject(":compose:material3:adaptive:adaptive-layout", [BuildType.COMPOSE])
includeProject(":compose:material3:adaptive:adaptive-navigation", [BuildType.COMPOSE])
+includeProject(":compose:material3:adaptive:adaptive-render-strategy", [BuildType.COMPOSE])
includeProject(":compose:material3:adaptive:adaptive-samples", "compose/material3/adaptive/samples", [BuildType.COMPOSE])
includeProject(":compose:material3:adaptive:adaptive-benchmark", "compose/material3/adaptive/benchmark", [BuildType.COMPOSE])
includeProject(":compose:material3:material3", [BuildType.COMPOSE])
@@ -596,6 +599,7 @@
includeProject(":contentpager:contentpager", [BuildType.MAIN])
includeProject(":coordinatorlayout:coordinatorlayout", [BuildType.MAIN])
includeProject(":core:core", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":core:core:core-samples", "core/core/samples", [BuildType.MAIN])
includeProject(":core:core-testing", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE])
includeProject(":core:core:integration-tests:publishing", [BuildType.MAIN])
includeProject(":core:core-animation", [BuildType.MAIN])
@@ -654,6 +658,7 @@
includeProject(":datastore:datastore-rxjava2", [BuildType.MAIN, BuildType.INFRAROGUE, BuildType.KMP])
includeProject(":datastore:datastore-rxjava3", [BuildType.MAIN, BuildType.INFRAROGUE, BuildType.KMP])
includeProject(":datastore:datastore-sampleapp", [BuildType.MAIN, BuildType.INFRAROGUE, BuildType.KMP])
+includeProject(":datastore:integration-tests:testapp", [BuildType.MAIN])
includeProject(":documentfile:documentfile", [BuildType.MAIN])
includeProject(":draganddrop:draganddrop", [BuildType.MAIN])
includeProject(":draganddrop:integration-tests:sampleapp", [BuildType.MAIN])
@@ -971,7 +976,6 @@
includeProject(":tv:tv-foundation", [BuildType.COMPOSE])
includeProject(":tv:tv-material", [BuildType.COMPOSE])
includeProject(":tv:integration-tests:playground", [BuildType.COMPOSE])
-includeProject(":tv:integration-tests:presentation", [BuildType.COMPOSE])
includeProject(":tv:integration-tests:macrobenchmark", [BuildType.COMPOSE])
includeProject(":tv:integration-tests:macrobenchmark-target", [BuildType.COMPOSE])
includeProject(":tv:tv-material-samples", "tv/tv-material/samples", [BuildType.COMPOSE])
diff --git a/slice/slice-core/api/restricted_current.txt b/slice/slice-core/api/restricted_current.txt
index bbe0f72..45a6740 100644
--- a/slice/slice-core/api/restricted_current.txt
+++ b/slice/slice-core/api/restricted_current.txt
@@ -243,7 +243,7 @@
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static int parseImageMode(androidx.slice.SliceItem);
method @Deprecated public void setActivity(boolean);
method @Deprecated public androidx.slice.core.SliceActionImpl setChecked(boolean);
- method @Deprecated public androidx.slice.core.SliceAction? setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.core.SliceAction setContentDescription(CharSequence);
method @Deprecated public androidx.slice.core.SliceActionImpl setKey(String);
method @Deprecated public androidx.slice.core.SliceActionImpl setPriority(@IntRange(from=0) int);
}
diff --git a/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java b/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java
index 42ae49f..92d1c47 100644
--- a/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java
+++ b/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java
@@ -263,7 +263,6 @@
* @param description the content description for this action.
* @return
*/
- @Nullable
@Override
public @NonNull SliceAction setContentDescription(@NonNull CharSequence description) {
mContentDescription = description;
diff --git a/slice/slice-test/src/main/java/androidx/slice/test/SampleSliceProvider.java b/slice/slice-test/src/main/java/androidx/slice/test/SampleSliceProvider.java
index 379b763..150979e 100644
--- a/slice/slice-test/src/main/java/androidx/slice/test/SampleSliceProvider.java
+++ b/slice/slice-test/src/main/java/androidx/slice/test/SampleSliceProvider.java
@@ -1680,7 +1680,7 @@
private SetHostExtraApi21Impl() {}
static void setHostExtra(ListBuilder listBuilder, String key, String value) {
PersistableBundle extras = new PersistableBundle();
- extras.putString("tts", "hello world");
+ extras.putString(key, value);
// Attach additional information for host. Depending on the host apps, this
// information might or might not be used.
// In this case, SliceBrowser is customized to play TTS when binding the slice.
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java b/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java
index e05be8d..4287f34 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java
@@ -354,7 +354,6 @@
*
* @return the current value of a progress bar or input range associated with this slice.
*/
- @NonNull
public int getRangeValue() {
if (mTemplateType == ROW_TYPE_SLIDER
|| mTemplateType == ROW_TYPE_PROGRESS) {
diff --git a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt
index a09be1b..86583ab 100644
--- a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt
+++ b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt
@@ -361,44 +361,6 @@
}
assertWithMessage("non-transparent pixels were drawn").that(hasNonTransparentPixel).isTrue()
}
-
- @Test
- fun skippedMeasurePassIsCorrected() {
- val context = InstrumentationRegistry.getInstrumentation().context
- val spl = createTestSpl(context, collapsibleContentViews = true)
-
- fun assertAdjacentSiblings(message: String) {
- val (leftChild, rightChild) = spl.leftAndRightViews()
- assertWithMessage("adjacent view edges: $message")
- .that(rightChild.left)
- .isEqualTo(leftChild.right)
- }
-
- assertAdjacentSiblings("initial layout")
-
- spl.splitDividerPosition = 0
- spl.measureAndLayoutForTest()
-
- assertAdjacentSiblings("with splitDividerPosition = 0")
-
- val (left, right) = spl.leftAndRightViews()
- assertWithMessage("left child width").that(left.width).isEqualTo(0)
- assertWithMessage("right child width").that(right.width).isEqualTo(100)
- }
-}
-
-private fun SlidingPaneLayout.leftAndRightViews(): Pair<View, View> {
- val isRtl = this.layoutDirection == View.LAYOUT_DIRECTION_RTL
- val leftChild: View
- val rightChild: View
- if (isRtl) {
- leftChild = this[1]
- rightChild = this[0]
- } else {
- leftChild = this[0]
- rightChild = this[1]
- }
- return leftChild to rightChild
}
private fun View.drawToBitmap(): Bitmap {
@@ -411,40 +373,31 @@
private fun createTestSpl(
context: Context,
setDividerDrawable: Boolean = true,
- childPanesAcceptTouchEvents: Boolean = false,
- collapsibleContentViews: Boolean = false
+ childPanesAcceptTouchEvents: Boolean = false
): SlidingPaneLayout =
SlidingPaneLayout(context).apply {
addView(
TestPaneView(context).apply {
- val lpWidth: Int
- if (collapsibleContentViews) {
- lpWidth = 0
- } else {
- minimumWidth = 30
- lpWidth = LayoutParams.WRAP_CONTENT
- }
+ minimumWidth = 30
acceptTouchEvents = childPanesAcceptTouchEvents
layoutParams =
- SlidingPaneLayout.LayoutParams(lpWidth, LayoutParams.MATCH_PARENT).apply {
- weight = 1f
- }
+ SlidingPaneLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT
+ )
+ .apply { weight = 1f }
}
)
addView(
TestPaneView(context).apply {
- val lpWidth: Int
- if (collapsibleContentViews) {
- lpWidth = 0
- } else {
- minimumWidth = 30
- lpWidth = LayoutParams.WRAP_CONTENT
- }
+ minimumWidth = 30
acceptTouchEvents = childPanesAcceptTouchEvents
layoutParams =
- SlidingPaneLayout.LayoutParams(lpWidth, LayoutParams.MATCH_PARENT).apply {
- weight = 1f
- }
+ SlidingPaneLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT
+ )
+ .apply { weight = 1f }
}
)
isUserResizingEnabled = true
diff --git a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
index e8b3a24..12434f8 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
+++ b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
@@ -192,11 +192,7 @@
}
private inline val View.spLayoutParams: SlidingPaneLayout.LayoutParams
- get() =
- when (val layoutParams = layoutParams) {
- is SlidingPaneLayout.LayoutParams -> layoutParams
- else -> layoutParamsError(this, layoutParams)
- }
+ get() = layoutParams as SlidingPaneLayout.LayoutParams
/**
* SlidingPaneLayout provides a horizontal, multi-pane layout for use at the top level of a UI. A
@@ -986,7 +982,7 @@
if (child.visibility == GONE) return@forEachIndexed
val lp = child.spLayoutParams
val skippedFirstPass = !lp.canInfluenceParentSize || lp.weightOnlyWidth
- val firstPassMeasuredWidth = if (skippedFirstPass) 0 else child.measuredWidth
+ val measuredWidth = if (skippedFirstPass) 0 else child.measuredWidth
val newWidth =
when {
// Child view consumes available space if the combined width cannot fit into
@@ -999,7 +995,7 @@
val widthToDistribute = widthRemaining.coerceAtLeast(0)
val addedWidth =
(lp.weight * widthToDistribute / weightSum).roundToInt()
- firstPassMeasuredWidth + addedWidth
+ measuredWidth + addedWidth
} else { // Explicit dividing line is defined
val clampedPos =
dividerPos
@@ -1019,9 +1015,9 @@
widthAvailable - lp.horizontalMargin - totalMeasuredWidth
}
lp.width > 0 -> lp.width
- else -> firstPassMeasuredWidth
+ else -> measuredWidth
}
- if (newWidth != child.measuredWidth) {
+ if (measuredWidth != newWidth) {
val childWidthSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY)
val childHeightSpec =
getChildHeightMeasureSpec(
diff --git a/stableaidl/stableaidl-gradle-plugin/build.gradle b/stableaidl/stableaidl-gradle-plugin/build.gradle
index 4518da8..a0c775c 100644
--- a/stableaidl/stableaidl-gradle-plugin/build.gradle
+++ b/stableaidl/stableaidl-gradle-plugin/build.gradle
@@ -21,7 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.*
+import androidx.build.KotlinTarget
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -85,4 +86,5 @@
type = LibraryType.GRADLE_PLUGIN
inceptionYear = "2022"
description = "Stable AIDL Gradle Plugin"
+ kotlinTarget = KotlinTarget.KOTLIN_1_9
}
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
index 1d4f54a..62969ec 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
@@ -140,7 +140,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressMenu();
- assertTrue(textView.wait(Until.textEquals("keycode menu pressed"), TIMEOUT_MS));
+ assertTrue(textView.wait(Until.textEquals("keycode menu pressed; "), TIMEOUT_MS));
}
@Test
@@ -149,7 +149,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressBack();
- assertTrue(textView.wait(Until.textEquals("keycode back pressed"), TIMEOUT_MS));
+ assertTrue(textView.wait(Until.textEquals("keycode back pressed; "), TIMEOUT_MS));
}
@Test
@@ -158,7 +158,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressSearch();
- assertTrue(textView.wait(Until.textEquals("keycode search pressed"), TIMEOUT_MS));
+ assertTrue(textView.wait(Until.textEquals("keycode search pressed; "), TIMEOUT_MS));
}
@Test
@@ -167,7 +167,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressDPadCenter();
- assertTrue(textView.wait(Until.textEquals("keycode dpad center pressed"), TIMEOUT_MS));
+ assertTrue(textView.wait(Until.textEquals("keycode dpad center pressed; "), TIMEOUT_MS));
}
@Test
@@ -176,7 +176,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressDPadDown();
- assertTrue(textView.wait(Until.textEquals("keycode dpad down pressed"), TIMEOUT_MS));
+ assertTrue(textView.wait(Until.textEquals("keycode dpad down pressed; "), TIMEOUT_MS));
}
@Test
@@ -185,7 +185,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressDPadUp();
- assertTrue(textView.wait(Until.textEquals("keycode dpad up pressed"), TIMEOUT_MS));
+ assertTrue(textView.wait(Until.textEquals("keycode dpad up pressed; "), TIMEOUT_MS));
}
@Test
@@ -194,7 +194,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressDPadLeft();
- assertTrue(textView.wait(Until.textEquals("keycode dpad left pressed"), TIMEOUT_MS));
+ assertTrue(textView.wait(Until.textEquals("keycode dpad left pressed; "), TIMEOUT_MS));
}
@Test
@@ -203,7 +203,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressDPadRight();
- assertTrue(textView.wait(Until.textEquals("keycode dpad right pressed"), TIMEOUT_MS));
+ assertTrue(textView.wait(Until.textEquals("keycode dpad right pressed; "), TIMEOUT_MS));
}
@Test
@@ -212,7 +212,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressDelete();
- assertTrue(textView.wait(Until.textEquals("keycode delete pressed"), TIMEOUT_MS));
+ assertTrue(textView.wait(Until.textEquals("keycode delete pressed; "), TIMEOUT_MS));
}
@Test
@@ -221,7 +221,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressEnter();
- assertTrue(textView.wait(Until.textEquals("keycode enter pressed"), TIMEOUT_MS));
+ assertTrue(textView.wait(Until.textEquals("keycode enter pressed; "), TIMEOUT_MS));
}
@Test
@@ -230,7 +230,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressKeyCode(KeyEvent.KEYCODE_0);
- assertTrue(textView.wait(Until.textEquals("keycode 0 pressed"), TIMEOUT_MS));
+ assertTrue(textView.wait(Until.textEquals("keycode 0 pressed; "), TIMEOUT_MS));
}
@Test
@@ -240,7 +240,31 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressKeyCode(KeyEvent.KEYCODE_Z,
KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON);
- assertTrue(textView.wait(Until.textEquals("keycode Z pressed with meta shift left on"),
+ assertTrue(textView.wait(Until.textEquals("keycode Z pressed; with meta shift left on; "),
+ TIMEOUT_MS));
+ }
+
+ @Test
+ public void testPressKeyCodes_withMetaKeyCodes() {
+ launchTestActivity(KeycodeTestActivity.class);
+
+ UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
+ mDevice.pressKeyCodes(new int[]{KeyEvent.KEYCODE_Z,
+ KeyEvent.KEYCODE_SHIFT_LEFT});
+ assertTrue(textView.wait(Until.textEquals(
+ "keycode Z pressed; keycode shift left pressed; with meta shift left on; "),
+ TIMEOUT_MS));
+ }
+
+ @Test
+ public void testPressKeyCodes_withMetaKeyCodesReverseOrder() {
+ launchTestActivity(KeycodeTestActivity.class);
+
+ UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
+ mDevice.pressKeyCodes(new int[]{KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_Z});
+ assertTrue(textView.wait(Until.textEquals(
+ "keycode shift left pressed; with meta shift left on; keycode Z pressed;"
+ + " with meta shift left on; "),
TIMEOUT_MS));
}
@@ -261,7 +285,7 @@
UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
mDevice.pressKeyCodes(new int[]{KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_B});
- assertTrue(textView.wait(Until.textEquals("keycode A and keycode B are pressed"),
+ assertTrue(textView.wait(Until.textEquals("keycode A pressed; keycode B pressed; "),
TIMEOUT_MS));
}
diff --git a/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/KeycodeTestActivity.java b/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/KeycodeTestActivity.java
index a4e91d8..d162bf2 100644
--- a/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/KeycodeTestActivity.java
+++ b/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/KeycodeTestActivity.java
@@ -31,6 +31,8 @@
super.onCreate(savedInstanceState);
setContentView(R.layout.keycode_test_activity);
+ TextView textView = (TextView) findViewById(R.id.text_view);
+ textView.setText("");
}
@Override
@@ -38,55 +40,54 @@
TextView textView = (TextView) findViewById(R.id.text_view);
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
- textView.setText("keycode back pressed");
+ textView.append("keycode back pressed; ");
break;
case KeyEvent.KEYCODE_DEL:
- textView.setText("keycode delete pressed");
+ textView.append("keycode delete pressed; ");
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
- textView.setText("keycode dpad center pressed");
+ textView.append("keycode dpad center pressed; ");
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
- textView.setText("keycode dpad down pressed");
+ textView.append("keycode dpad down pressed; ");
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
- textView.setText("keycode dpad left pressed");
+ textView.append("keycode dpad left pressed; ");
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
- textView.setText("keycode dpad right pressed");
+ textView.append("keycode dpad right pressed; ");
break;
case KeyEvent.KEYCODE_DPAD_UP:
- textView.setText("keycode dpad up pressed");
+ textView.append("keycode dpad up pressed; ");
break;
case KeyEvent.KEYCODE_ENTER:
- textView.setText("keycode enter pressed");
+ textView.append("keycode enter pressed; ");
break;
case KeyEvent.KEYCODE_MENU:
- textView.setText("keycode menu pressed");
+ textView.append("keycode menu pressed; ");
break;
case KeyEvent.KEYCODE_SEARCH:
- textView.setText("keycode search pressed");
- break;
- case KeyEvent.KEYCODE_Z:
- textView.setText("keycode Z pressed");
- break;
- case KeyEvent.KEYCODE_0:
- textView.setText("keycode 0 pressed");
+ textView.append("keycode search pressed; ");
break;
case KeyEvent.KEYCODE_A:
- if (mLastPressedKeyCode == KeyEvent.KEYCODE_B) {
- textView.setText("keycode A and keycode B are pressed");
- }
+ textView.append("keycode A pressed; ");
break;
case KeyEvent.KEYCODE_B:
- if (mLastPressedKeyCode == KeyEvent.KEYCODE_A) {
- textView.setText("keycode A and keycode B are pressed");
- }
+ textView.append("keycode B pressed; ");
+ break;
+ case KeyEvent.KEYCODE_Z:
+ textView.append("keycode Z pressed; ");
+ break;
+ case KeyEvent.KEYCODE_0:
+ textView.append("keycode 0 pressed; ");
+ break;
+ case KeyEvent.KEYCODE_SHIFT_LEFT:
+ textView.append("keycode shift left pressed; ");
break;
}
if ((event.getMetaState() & (KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON)) != 0) {
- textView.append(" with meta shift left on");
+ textView.append("with meta shift left on; ");
}
mLastPressedKeyCode = keyCode;
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
index d2c5eb7..9f71e70 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
@@ -34,6 +34,8 @@
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
@@ -61,6 +63,30 @@
// Inserted after each motion event injection.
private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
+ private static final Map<Integer, Integer> KEY_MODIFIER = new HashMap<>();
+
+ static {
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_SHIFT_RIGHT,
+ KeyEvent.META_SHIFT_RIGHT_ON | KeyEvent.META_SHIFT_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_ALT_RIGHT,
+ KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_ALT_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_SYM, KeyEvent.META_SYM_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_FUNCTION, KeyEvent.META_FUNCTION_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_CTRL_RIGHT,
+ KeyEvent.META_CTRL_RIGHT_ON | KeyEvent.META_CTRL_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_META_LEFT, KeyEvent.META_META_LEFT_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_META_RIGHT, KeyEvent.META_META_RIGHT_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_CAPS_LOCK, KeyEvent.META_CAPS_LOCK_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_NUM_LOCK, KeyEvent.META_NUM_LOCK_ON);
+ KEY_MODIFIER.put(KeyEvent.KEYCODE_SCROLL_LOCK, KeyEvent.META_SCROLL_LOCK_ON);
+ }
+
InteractionController(UiDevice device) {
mDevice = device;
}
@@ -403,6 +429,9 @@
public boolean sendKeys(int[] keyCodes, int metaState) {
final long eventTime = SystemClock.uptimeMillis();
for (int keyCode : keyCodes) {
+ if (KEY_MODIFIER.containsKey(keyCode)) {
+ metaState |= KEY_MODIFIER.get(keyCode);
+ }
KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
@@ -417,6 +446,9 @@
if (!injectEventSync(upEvent)) {
return false;
}
+ if (KEY_MODIFIER.containsKey(keyCode)) {
+ metaState &= ~KEY_MODIFIER.get(keyCode);
+ }
}
return true;
}
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
index 1c189a3..747addd 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
@@ -481,7 +481,8 @@
}
/**
- * Presses one or more keys.
+ * Presses one or more keys. Keys that change meta state are supported, and will apply their
+ * meta state to following keys.
* <br/>
* For example, you can simulate taking a screenshot on the device by pressing both the
* power and volume down keys.
@@ -497,7 +498,8 @@
}
/**
- * Presses one or more keys.
+ * Presses one or more keys. Keys that change meta state are supported, and will apply their
+ * meta state to following keys.
* <br/>
* For example, you can simulate taking a screenshot on the device by pressing both the
* power and volume down keys.
diff --git a/transition/transition/src/androidTest/java/androidx/transition/TransitionManagerTest.java b/transition/transition/src/androidTest/java/androidx/transition/TransitionManagerTest.java
index c80073a..a4b47e8 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/TransitionManagerTest.java
+++ b/transition/transition/src/androidTest/java/androidx/transition/TransitionManagerTest.java
@@ -20,16 +20,21 @@
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import android.os.Build;
+import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.MediumTest;
+import androidx.test.filters.SdkSuppress;
import androidx.testutils.AnimationDurationScaleRule;
import androidx.transition.test.R;
@@ -37,6 +42,9 @@
import org.junit.Rule;
import org.junit.Test;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
@MediumTest
public class TransitionManagerTest extends BaseTest {
@@ -218,4 +226,48 @@
verify(listener, never()).onTransitionEnd(any(Transition.class));
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ public void endTransitionOnTransitionCancel() throws Throwable {
+ final ViewGroup root = rule.getActivity().getRoot();
+
+ View[] views = new View[] {
+ new View(root.getContext()),
+ new View(root.getContext()),
+ new View(root.getContext()),
+ new View(root.getContext()),
+ new View(root.getContext())
+ };
+
+ for (View view : views) {
+ CountDownLatch latch = new CountDownLatch(1);
+ rule.runOnUiThread(() -> {
+ Fade fadeIn = new Fade();
+ fadeIn.addListener(new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionCancel(@NonNull Transition transition) {
+ TransitionManager.endTransitions(root);
+ }
+ });
+ TransitionSeekController seekController =
+ TransitionManager.controlDelayedTransition(root, fadeIn);
+ ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(10, 10);
+ root.addView(view, layoutParams);
+ assert seekController != null;
+ seekController.addOnReadyListener(transitionSeekController -> latch.countDown());
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+ }
+ for (View view : views) {
+ CountDownLatch latch = new CountDownLatch(1);
+ rule.runOnUiThread(() -> {
+ TransitionSeekController seekController =
+ TransitionManager.controlDelayedTransition(root, new Fade());
+ root.removeView(view);
+ assert seekController != null;
+ seekController.addOnReadyListener(transitionSeekController -> latch.countDown());
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+ }
+ }
}
diff --git a/transition/transition/src/main/java/androidx/transition/Transition.java b/transition/transition/src/main/java/androidx/transition/Transition.java
index f4fc52a..a4b20ea 100644
--- a/transition/transition/src/main/java/androidx/transition/Transition.java
+++ b/transition/transition/src/main/java/androidx/transition/Transition.java
@@ -1883,6 +1883,7 @@
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
int numOldAnims = runningAnimators.size();
WindowId windowId = sceneRoot.getWindowId();
+ ArrayList<Transition> endedTransitions = new ArrayList<>();
for (int i = numOldAnims - 1; i >= 0; i--) {
Animator anim = runningAnimators.keyAt(i);
if (anim != null) {
@@ -1905,14 +1906,9 @@
// a listener
anim.cancel();
transition.mCurrentAnimators.remove(anim);
- runningAnimators.remove(anim);
+ runningAnimators.removeAt(i);
if (transition.mCurrentAnimators.size() == 0) {
- transition.notifyListeners(TransitionNotification.ON_CANCEL, false);
- if (!transition.mEnded) {
- transition.mEnded = true;
- transition.notifyListeners(TransitionNotification.ON_END,
- false);
- }
+ endedTransitions.add(transition);
}
} else if (anim.isRunning() || anim.isStarted()) {
if (DBG) {
@@ -1923,13 +1919,24 @@
if (DBG) {
Log.d(LOG_TAG, "removing anim from info list: " + anim);
}
- runningAnimators.remove(anim);
+ runningAnimators.removeAt(i);
}
}
}
}
}
+ // Don't change the collection we're iterating over while iterating over it.
+ for (int i = 0; i < endedTransitions.size(); i++) {
+ Transition transition = endedTransitions.get(i);
+ transition.notifyListeners(TransitionNotification.ON_CANCEL, false);
+ if (!transition.mEnded) {
+ transition.mEnded = true;
+ transition.notifyListeners(TransitionNotification.ON_END,
+ false);
+ }
+ }
+
createAnimators(sceneRoot, mStartValues, mEndValues, mStartValuesList, mEndValuesList);
if (mSeekController == null) {
runAnimators();
diff --git a/tv/integration-tests/presentation/README.md b/tv/integration-tests/presentation/README.md
deleted file mode 100644
index 9c46867..0000000
--- a/tv/integration-tests/presentation/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Presentation app
-
-## Setup
-
-* Uncomment the `coil` and `gson` libraries dependency additions from the `build.gradle` file.
-* Uncomment the function content and imports from
- `presentation/src/main/java/androidx/tv/integration/presentation/ExternalLibs.kt` file
-* Create the `data.json` file in `presentation/src/main/assets` directory and add the content from
- this link: go/compose-tv-presentation-app-data
-
-> If you are not a Googler and want to use this app for
-> testing, you will have to create the `data.json` file by following the schema mentioned in the
-`Data.kt` file
diff --git a/tv/integration-tests/presentation/build.gradle b/tv/integration-tests/presentation/build.gradle
deleted file mode 100644
index faab995..0000000
--- a/tv/integration-tests/presentation/build.gradle
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * This file was created using the `create_project.py` script located in the
- * `<AndroidX root>/development/project-creator` directory.
- *
- * Please use that script when creating a new project, rather than copying an existing project and
- * modifying its settings.
- */
-import androidx.build.LibraryType
-
-plugins {
- id("AndroidXPlugin")
- id("com.android.application")
- id("AndroidXComposePlugin")
- id("org.jetbrains.kotlin.android")
-}
-
-dependencies {
- implementation(libs.kotlinStdlib)
-
- def composeVersion = "1.6.7"
-
- implementation("androidx.activity:activity-compose:1.9.0")
- implementation("androidx.appcompat:appcompat:1.6.1")
- implementation("androidx.compose.animation:animation:$composeVersion")
- implementation("androidx.compose.foundation:foundation-layout:$composeVersion")
- implementation("androidx.compose.material:material-icons-core:$composeVersion")
- implementation("androidx.compose.material:material-icons-extended:$composeVersion")
- implementation("androidx.compose.material3:material3:1.2.1")
- implementation("androidx.compose.runtime:runtime:$composeVersion")
- implementation("androidx.compose.ui:ui:$composeVersion")
- implementation("androidx.navigation:navigation-runtime:2.7.7")
- implementation("androidx.profileinstaller:profileinstaller:1.3.1")
- implementation(project(":tv:tv-foundation"))
- implementation(project(":tv:tv-material"))
-}
-
-android {
- compileSdk 35
- defaultConfig {
- minSdkVersion 28
- }
-
- buildTypes {
- release {
- minifyEnabled true
- shrinkResources true
- proguardFiles getDefaultProguardFile('proguard-android.txt')
- signingConfig signingConfigs.debug
- }
- }
- namespace "androidx.tv.integration.presentation"
-}
diff --git a/tv/integration-tests/presentation/lint-baseline.xml b/tv/integration-tests/presentation/lint-baseline.xml
deleted file mode 100644
index f05a7c4..0000000
--- a/tv/integration-tests/presentation/lint-baseline.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.6.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.6.0-beta01)" variant="all" version="8.6.0-beta01">
-
- <issue
- id="ModifierParameter"
- message="Modifier parameter should be the first optional parameter"
- errorLine1="fun FeaturedCarousel(movies: List<Movie> = featuredCarouselMovies, modifier: Modifier = Modifier) {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt"/>
- </issue>
-
- <issue
- id="UnnecessaryLambdaCreation"
- message="Creating an unnecessary lambda to emit a captured lambda"
- errorLine1=" content()"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/tv/integration/presentation/AlignmentCenter.kt"/>
- </issue>
-
- <issue
- id="AutoboxingStateCreation"
- message="Prefer `mutableIntStateOf` instead of `mutableStateOf`"
- errorLine1=" var selectedTabIndex by remember { mutableStateOf(0) }"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/tv/integration/presentation/App.kt"/>
- </issue>
-
- <issue
- id="AutoboxingStateCreation"
- message="Prefer `mutableFloatStateOf` instead of `mutableStateOf`"
- errorLine1=" var height by remember { mutableStateOf(0f) }"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/tv/integration/presentation/LandscapeImageBackground.kt"/>
- </issue>
-
-</issues>
diff --git a/tv/integration-tests/presentation/src/main/AndroidManifest.xml b/tv/integration-tests/presentation/src/main/AndroidManifest.xml
deleted file mode 100644
index 3a078f4..0000000
--- a/tv/integration-tests/presentation/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2023 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"
- xmlns:tools="http://schemas.android.com/tools">
-
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:supportsRtl="true"
- android:theme="@style/Theme.Androidx">
- <activity
- android:name=".MainActivity"
- android:banner="@drawable/app_icon_your_company"
- android:exported="true"
- android:icon="@drawable/app_icon_your_company"
- android:label="@string/app_name"
- android:logo="@drawable/app_icon_your_company"
- android:screenOrientation="landscape">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
- </intent-filter>
- </activity>
-
- </application>
-
- <uses-feature
- android:name="android.software.leanback"
- android:required="true" />
- <uses-feature
- android:name="android.hardware.touchscreen"
- android:required="false" />
-
- <uses-permission android:name="android.permission.INTERNET" />
-
-</manifest>
\ No newline at end of file
diff --git a/tv/integration-tests/presentation/src/main/assets/.gitignore b/tv/integration-tests/presentation/src/main/assets/.gitignore
deleted file mode 100644
index 114ea57..0000000
--- a/tv/integration-tests/presentation/src/main/assets/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-data.json
\ No newline at end of file
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AlignmentCenter.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AlignmentCenter.kt
deleted file mode 100644
index 9284cd6..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AlignmentCenter.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-
-@Composable
-fun AlignmentCenter(
- modifier: Modifier = Modifier,
- horizontalAxis: Boolean = false,
- verticalAxis: Boolean = false,
- content: @Composable RowScope.() -> Unit
-) {
- Row(
- modifier = modifier.fillMaxWidth(),
- horizontalArrangement = if (horizontalAxis) Arrangement.Center else Arrangement.Start,
- verticalAlignment = if (verticalAxis) Alignment.CenterVertically else Alignment.Top,
- ) {
- content()
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/App.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/App.kt
deleted file mode 100644
index 798aa78..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/App.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.input.key.key
-import androidx.compose.ui.input.key.nativeKeyCode
-import androidx.compose.ui.input.key.onKeyEvent
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.zIndex
-import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.ModalNavigationDrawer
-
-val pageColor = Color(0xff18171a)
-
-enum class Tabs(val displayName: String, val action: @Composable () -> Unit) {
- Home(
- "Home",
- {
- LazyColumn(
- modifier = Modifier.fillMaxSize().focusRequester(Home.fr).background(pageColor)
- ) {
- item {
- FeaturedCarousel()
- AppSpacer(height = 50.dp)
- }
- movieCollections.forEach { movieCollection ->
- item {
- AppLazyRow(
- title = movieCollection.label,
- items = movieCollection.items,
- drawItem = { movie, _, modifier ->
- ImageCard(movie, aspectRatio = 2f / 3, modifier = modifier)
- }
- )
- AppSpacer(height = 35.dp)
- }
- }
- }
- }
- ),
- Shows(
- "Shows",
- {
- LazyColumn(modifier = Modifier.fillMaxSize().background(pageColor)) {
- item { ShowsGrid(Modifier.focusRequester(Shows.fr)) }
- }
- }
- );
-
- val fr: FocusRequester = FocusRequester()
-}
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-fun App() {
- val tabs = remember { Tabs.values() }
- var selectedTabIndex by remember { mutableStateOf(0) }
- val activeTab = remember(selectedTabIndex) { tabs[selectedTabIndex] }
-
- val tabRow =
- @Composable {
- AppTabRow(
- tabs = tabs.map { it.displayName },
- selectedTabIndex = selectedTabIndex,
- onSelectedTabIndexChange = { selectedTabIndex = it },
- modifier =
- Modifier.zIndex(100f).onKeyEvent {
- if (it.key.nativeKeyCode == Key.DirectionDown.nativeKeyCode) {
- activeTab.fr.requestFocus()
- true
- } else false
- }
- )
- }
-
- val activePage: MutableState<(@Composable () -> Unit)> =
- remember(selectedTabIndex) { mutableStateOf(activeTab.action) }
-
- ModalNavigationDrawer(
- drawerContent = {
- Sidebar(selectedIndex = selectedTabIndex, onIndexChange = { selectedTabIndex = it })
- }
- ) {
- Box(modifier = Modifier.fillMaxSize()) {
- activePage.value()
- tabRow()
- }
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppButton.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppButton.kt
deleted file mode 100644
index 39af722..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppButton.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Icon
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.Text
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-fun AppButton(
- text: String,
- icon: ImageVector,
- modifier: Modifier = Modifier,
-) {
- var isFocused by remember { mutableStateOf(false) }
-
- Row(
- modifier =
- modifier
- .border(
- 2.dp,
- if (isFocused) Color.White else Color.Transparent,
- RoundedCornerShape(50)
- )
- .padding(4.dp)
- .background(Color.White.copy(alpha = 0.9f), RoundedCornerShape(50))
- .padding(top = 5.dp, bottom = 5.dp, start = 10.dp, end = 15.dp)
- .onFocusChanged { isFocused = it.isFocused }
- .focusable(),
- horizontalArrangement = Arrangement.spacedBy(0.dp),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Icon(
- imageVector = icon,
- contentDescription = null,
- modifier = Modifier.size(25.dp),
- tint = Color.Black
- )
- Text(text = text, fontSize = 12.sp)
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppLazyRow.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppLazyRow.kt
deleted file mode 100644
index 2478470..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppLazyRow.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.Text
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-fun AppLazyRow(
- title: String,
- items: List<Movie>,
- modifier: Modifier = Modifier,
- drawItem: @Composable (movie: Movie, index: Int, modifier: Modifier) -> Unit
-) {
- val paddingLeft = 58.dp
- var hasFocus by remember { mutableStateOf(false) }
-
- Column(modifier = modifier.onFocusChanged { hasFocus = it.hasFocus }) {
- Text(
- text = title,
- color = if (hasFocus) Color.White else Color.White.copy(alpha = 0.8f),
- fontSize = 14.sp,
- modifier = Modifier.padding(start = paddingLeft)
- )
-
- AppSpacer(height = 12.dp)
-
- LazyRow(
- contentPadding = PaddingValues(horizontal = paddingLeft),
- horizontalArrangement = Arrangement.spacedBy(20.dp),
- ) {
- items.forEachIndexed { index, movie -> item { drawItem(movie, index, Modifier) } }
- }
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppSpacer.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppSpacer.kt
deleted file mode 100644
index b912325..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppSpacer.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Dp
-
-@Composable
-fun AppSpacer(width: Dp? = null, height: Dp? = null) {
- var modifier: Modifier = Modifier
- if (width != null) {
- modifier = modifier.width(width)
- }
- if (height != null) {
- modifier = modifier.height(height)
- }
- Spacer(modifier)
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppTabRow.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppTabRow.kt
deleted file mode 100644
index c2dae79..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/AppTabRow.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.key
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.LocalContentColor
-import androidx.tv.material3.Tab
-import androidx.tv.material3.TabDefaults
-import androidx.tv.material3.TabRow
-import androidx.tv.material3.Text
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-fun AppTabRow(
- tabs: List<String>,
- selectedTabIndex: Int,
- onSelectedTabIndexChange: (Int) -> Unit,
- modifier: Modifier = Modifier
-) {
- AlignmentCenter(horizontalAxis = true) {
- TabRow(
- selectedTabIndex = selectedTabIndex,
- separator = { Spacer(modifier = Modifier.width(4.dp)) },
- modifier = modifier.padding(top = 20.dp),
- // indicator = @Composable { tabPositions ->
- // tabPositions.getOrNull(selectedTabIndex)?.let {
- // TabRowDefaults.PillIndicator(
- // currentTabPosition = it,
- // inactiveColor = Color(0xFFE5E1E6),
- // )
- // }
- // }
- ) {
- tabs.forEachIndexed { index, tabLabel ->
- key(index) {
- Tab(
- selected = selectedTabIndex == index,
- onFocus = { onSelectedTabIndexChange(index) },
- colors =
- TabDefaults.pillIndicatorTabColors(
- inactiveContentColor = LocalContentColor.current,
- // selectedContentColor =
- // Color(0xFF313033),
- ),
- modifier = Modifier,
- ) {
- Text(
- text = tabLabel,
- fontSize = 12.sp,
- modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp)
- )
- }
- }
- }
- }
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/BringIntoViewIfChildrenAreFocused.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/BringIntoViewIfChildrenAreFocused.kt
deleted file mode 100644
index 486221b..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/BringIntoViewIfChildrenAreFocused.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.relocation.BringIntoViewResponder
-import androidx.compose.foundation.relocation.bringIntoViewResponder
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.debugInspectorInfo
-
-@OptIn(ExperimentalFoundationApi::class)
-internal fun Modifier.bringIntoViewIfChildrenAreFocused(): Modifier =
- composed(
- inspectorInfo = debugInspectorInfo { name = "bringIntoViewIfChildrenAreFocused" },
- factory = {
- var myRect: Rect = Rect.Zero
- this.onSizeChanged {
- myRect = Rect(Offset.Zero, Offset(it.width.toFloat(), it.height.toFloat()))
- }
- .bringIntoViewResponder(
- remember {
- object : BringIntoViewResponder {
- // return the current rectangle and ignoring the child rectangle
- // received.
- @ExperimentalFoundationApi
- override fun calculateRectForParent(localRect: Rect): Rect = myRect
-
- // The container is not expected to be scrollable. Hence the child is
- // already in view with respect to the container.
- @ExperimentalFoundationApi
- override suspend fun bringChildIntoView(localRect: () -> Rect?) {}
- }
- }
- )
- }
- )
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/Data.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/Data.kt
deleted file mode 100644
index 422f26f..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/Data.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-data class MovieImage(val url: String, val aspect: String)
-
-data class Movie(val name: String, val images: List<MovieImage>, val root: String)
-
-data class MovieCollection(val label: String, val items: List<Movie>)
-
-data class RootData(
- val data: List<MovieCollection>,
- val featuredCarouselMovies: List<String>,
- val commonDescription: String
-)
-
-var movieCollections = listOf<MovieCollection>()
-var topPicksForYou = listOf<Movie>()
-var allMovies = listOf<Movie>()
-var featuredCarouselMovies = listOf<Movie>()
-var commonDescription = ""
-
-val Movie.description: String
- get() = commonDescription
-
-fun getMovieImageUrl(movie: Movie, aspect: String = "orientation/backdrop_16x9"): String =
- movie.images.find { image -> image.aspect == aspect }?.url ?: movie.images.first().url
-
-fun initializeData(rootData: RootData) {
- commonDescription = rootData.commonDescription
- movieCollections = rootData.data
- topPicksForYou = movieCollections[3].items
- allMovies = movieCollections.flatMap { it.items }.reversed()
- featuredCarouselMovies = run {
- val titles = rootData.featuredCarouselMovies
- val previousTitles = mutableListOf<String>()
-
- movieCollections
- .flatMap { it.items }
- .filter {
- if (previousTitles.contains(it.name)) {
- false
- } else {
- previousTitles.add(it.name)
- titles.contains(it.name)
- }
- }
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ExternalLibs.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ExternalLibs.kt
deleted file mode 100644
index 54919ea..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ExternalLibs.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import android.util.Log
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.ContentScale
-
-// import coil.compose.AsyncImage
-// import com.google.gson.Gson
-
-fun getRootDataFromJson(jsonData: String): RootData {
- Log.d("LOL", "getRootDataFromJson: $jsonData")
- // return Gson().fromJson(jsonData, RootData::class.java)
- return RootData(listOf(), listOf(), "")
-}
-
-@Composable
-fun AppAsyncImage(
- imageUrl: String,
- modifier: Modifier = Modifier,
- contentScale: ContentScale = ContentScale.Fit,
- alignment: Alignment = Alignment.Center,
- contentDescription: String? = null
-) {
- Log.d(
- "LOL",
- "AppAsyncImage: $imageUrl, $modifier, $contentScale, $alignment, $contentDescription"
- )
- // AsyncImage(
- // model = imageUrl,
- // contentScale = contentScale,
- // alignment = alignment,
- // modifier = modifier,
- // contentDescription = contentDescription
- // )
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt
deleted file mode 100644
index f997d1d..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.animation.AnimatedContentScope
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.slideInHorizontally
-import androidx.compose.animation.slideOutHorizontally
-import androidx.compose.animation.togetherWith
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.outlined.ArrowForward
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.tv.material3.Carousel
-import androidx.tv.material3.CarouselDefaults
-import androidx.tv.material3.CarouselState
-import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.Text
-import androidx.tv.material3.rememberCarouselState
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-fun FeaturedCarousel(movies: List<Movie> = featuredCarouselMovies, modifier: Modifier = Modifier) {
- val carouselState: CarouselState = rememberCarouselState()
- val slidesCount = movies.size
-
- Carousel(
- itemCount = slidesCount,
- carouselState = carouselState,
- modifier = modifier.height(340.dp).fillMaxWidth(),
- carouselIndicator = {
- CarouselDefaults.IndicatorRow(
- itemCount = slidesCount,
- activeItemIndex = carouselState.activeItemIndex,
- modifier = Modifier.align(Alignment.BottomEnd).padding(end = 58.dp, bottom = 16.dp),
- )
- },
- contentTransformEndToStart = fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))),
- contentTransformStartToEnd = fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000)))
- ) { itemIndex ->
- val movie = movies[itemIndex]
-
- CarouselSlide(
- title = movie.name,
- description = movie.description,
- background = { LandscapeImageBackground(movie) },
- actions = {
- @Suppress("DEPRECATION")
- AppButton(
- text = "Watch on YouTube",
- icon = Icons.AutoMirrored.Outlined.ArrowForward,
- )
- },
- )
- }
-}
-
-@OptIn(ExperimentalAnimationApi::class, ExperimentalTvMaterial3Api::class)
-@Composable
-private fun AnimatedContentScope.CarouselSlide(
- title: String,
- description: String,
- background: @Composable () -> Unit,
- actions: @Composable () -> Unit
-) {
- Box {
- background()
- Column(
- modifier =
- Modifier.padding(start = 58.dp, top = 150.dp)
- .animateEnterExit(
- enter = slideInHorizontally(animationSpec = tween(1000)) { it / 2 },
- exit = slideOutHorizontally(animationSpec = tween(1000))
- )
- ) {
- Text(text = title, color = Color.White, fontSize = 40.sp)
-
- AppSpacer(height = 16.dp)
-
- Text(
- text = description,
- color = Color.White,
- fontSize = 16.sp,
- lineHeight = 24.sp,
- modifier = Modifier.width(500.dp),
- )
-
- AppSpacer(height = 15.dp)
-
- actions()
- }
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ImageCard.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ImageCard.kt
deleted file mode 100644
index 45efe7f..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ImageCard.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.animation.animateColorAsState
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.foundation.border
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.draw.scale
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.Text
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-fun ImageCard(
- movie: Movie,
- aspectRatio: Float = 16f / 9,
- customCardWidth: Dp? = null,
- modifier: Modifier = Modifier,
-) {
- val aspect =
- if (aspectRatio == 16f / 9) "orientation/vod_art_16x9" else "orientation/vod_art_2x3"
- val scaleMax = if (aspectRatio == 16f / 9) 1.1f else 1.025f
- val cardWidth = customCardWidth ?: if (aspectRatio == 16f / 9) 200.dp else 172.dp
-
- var isFocused by remember { mutableStateOf(false) }
- val shape = RoundedCornerShape(12.dp)
- val borderColor by
- animateColorAsState(
- targetValue = if (isFocused) Color.White.copy(alpha = 0.8f) else Color.Transparent
- )
- val scale by animateFloatAsState(targetValue = if (isFocused) scaleMax else 1f)
-
- Column(modifier = Modifier.width(cardWidth).scale(scale)) {
- Box(
- modifier =
- modifier
- .fillMaxWidth()
- .aspectRatio(aspectRatio)
- .border(2.dp, borderColor, shape)
- .clip(shape)
- .onFocusChanged { isFocused = it.isFocused }
- .focusable()
- ) {
- AppAsyncImage(imageUrl = getMovieImageUrl(movie, aspect = aspect))
- }
- androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(6.dp))
- Text(
- text = movie.name,
- color = Color.White.copy(alpha = 0.8f),
- fontSize = 12.sp,
- textAlign = TextAlign.Center,
- modifier = Modifier.fillMaxWidth()
- )
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/LandscapeImageBackground.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/LandscapeImageBackground.kt
deleted file mode 100644
index 1e2c8cb..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/LandscapeImageBackground.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.layout.onGloballyPositioned
-
-@Composable
-fun LandscapeImageBackground(movie: Movie, aspect: String = "orientation/iconic_16x9") {
- val navigationGradient =
- Brush.verticalGradient(
- colors = listOf(pageColor, Color.Transparent),
- startY = 0f,
- endY = 200f
- )
- var height by remember { mutableStateOf(0f) }
-
- val navigationGradientBottom =
- Brush.verticalGradient(
- colors = listOf(Color.Transparent, pageColor),
- startY = 50f,
- endY = height,
- )
-
- val horizontalGradient =
- Brush.horizontalGradient(
- colors = listOf(pageColor, Color.Transparent),
- startX = 1400f,
- endX = 900f,
- )
-
- Box(
- modifier = Modifier.fillMaxSize().onGloballyPositioned { height = it.size.height.toFloat() }
- ) {
- AppAsyncImage(
- imageUrl = getMovieImageUrl(movie = movie, aspect = aspect),
- contentScale = ContentScale.FillWidth,
- alignment = Alignment.Center,
- modifier = Modifier.fillMaxWidth(),
- contentDescription = null
- )
-
- Box(
- modifier =
- Modifier.matchParentSize()
- .background(navigationGradientBottom)
- .background(navigationGradient)
- .background(horizontalGradient)
- )
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/MainActivity.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/MainActivity.kt
deleted file mode 100644
index 7e03cfd..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/MainActivity.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-
-class MainActivity : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- // Create the "data.json" file in "presentation/src/main/assets" directory and add the
- // content from this link: go/compose-tv-presentation-app-data
- val jsonData = assets.readAssetsFile("data.json")
- val deserializedData = getRootDataFromJson(jsonData)
-
- initializeData(deserializedData)
-
- super.onCreate(savedInstanceState)
- setContent { App() }
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ShowsGrid.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ShowsGrid.kt
deleted file mode 100644
index 821f8db..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ShowsGrid.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
-import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.OutlinedTextFieldDefaults
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.dp
-import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.Text
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-fun ShowsGrid(modifier: Modifier = Modifier) {
- var keyword by remember { mutableStateOf("") }
- val movies by
- remember(keyword) {
- mutableStateOf(allMovies.filter { movie -> movie.name.contains(keyword) })
- }
- Column(
- modifier = Modifier.height(520.dp).padding(top = 70.dp),
- ) {
- Box(modifier = Modifier.padding(horizontal = 58.dp).fillMaxWidth()) {
- OutlinedTextField(
- value = keyword,
- onValueChange = { keyword = it },
- placeholder = { Text(text = "Search", color = Color.White) },
- modifier = modifier.fillMaxWidth().align(Alignment.Center),
- colors =
- OutlinedTextFieldDefaults.colors(
- focusedTextColor = Color.White,
- unfocusedTextColor = Color.White,
- focusedBorderColor = Color.White,
- unfocusedBorderColor = Color.White.copy(alpha = 0.5f)
- )
- )
- }
-
- AppSpacer(height = 20.dp)
-
- if (movies.isEmpty()) {
- AppSpacer(height = 20.dp)
- Box(modifier = Modifier.fillMaxWidth()) {
- Text(
- text = "No movies matched",
- modifier = Modifier.align(Alignment.Center),
- color = Color.White,
- )
- }
- }
-
- LazyHorizontalGrid(
- rows = GridCells.Fixed(3),
- contentPadding = PaddingValues(horizontal = 58.dp),
- verticalArrangement = Arrangement.spacedBy(10.dp),
- modifier = Modifier.fillMaxSize().bringIntoViewIfChildrenAreFocused(),
- ) {
- items(movies.size) {
- val movie = movies[it]
-
- Box(modifier = Modifier.padding(end = 30.dp)) {
- ImageCard(
- movie,
- customCardWidth = 150.dp,
- modifier = Modifier,
- )
- }
- }
- }
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/Sidebar.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/Sidebar.kt
deleted file mode 100644
index da1e056..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/Sidebar.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.focusGroup
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Home
-import androidx.compose.material.icons.outlined.Movie
-import androidx.compose.material.icons.outlined.Tv
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.IconButtonDefaults
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.key
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.unit.dp
-import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.Icon
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-fun Sidebar(
- selectedIndex: Int,
- onIndexChange: (index: Int) -> Unit,
-) {
- val fr = remember { FocusRequester() }
- val drawIcon: @Composable (imageVector: ImageVector, index: Int, modifier: Modifier) -> Unit =
- { imageVector, index, modifier ->
- var isFocused by remember { mutableStateOf(false) }
- val isSelected = selectedIndex == index
-
- IconButton(
- onClick = {},
- modifier =
- modifier
- .onFocusChanged {
- isFocused = it.isFocused
- if (it.isFocused) {
- onIndexChange(index)
- }
- }
- .focusRequester(if (index == 0) fr else FocusRequester()),
- colors =
- IconButtonDefaults.filledIconButtonColors(
- containerColor =
- if (isSelected && isFocused) Color.White else Color.Transparent,
- )
- ) {
- Box(modifier = Modifier) {
- Icon(
- imageVector = imageVector,
- tint = if (isSelected && isFocused) pageColor else Color.White,
- contentDescription = null,
- )
- if (isSelected) {
- Box(
- modifier =
- Modifier.width(10.dp)
- .height(3.dp)
- .offset(y = 4.dp)
- .align(Alignment.BottomCenter)
- .background(Color.Red)
- )
- }
- }
- }
- }
-
- Column(
- modifier = Modifier.width(60.dp).fillMaxHeight().background(pageColor).focusGroup(),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
- ) {
- key(0) {
- drawIcon(
- Icons.Outlined.Home,
- 0,
- Modifier,
- )
- }
- key(1) {
- drawIcon(
- Icons.Outlined.Movie,
- 1,
- Modifier,
- )
- }
- key(2) {
- drawIcon(
- Icons.Outlined.Tv,
- 2,
- Modifier,
- )
- }
- }
-}
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ifElse.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ifElse.kt
deleted file mode 100644
index 826d18b..0000000
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/ifElse.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2023 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.tv.integration.presentation
-
-import androidx.compose.ui.Modifier
-
-/** Thanks, Plex 🦄 :) */
-fun Modifier.ifElse(
- condition: () -> Boolean,
- ifTrueModifier: Modifier,
- ifFalseModifier: Modifier = Modifier
-): Modifier = then(if (condition()) ifTrueModifier else ifFalseModifier)
-
-fun Modifier.ifElse(
- condition: Boolean,
- ifTrueModifier: Modifier,
- ifFalseModifier: Modifier = Modifier
-): Modifier = ifElse({ condition }, ifTrueModifier, ifFalseModifier)
diff --git a/tv/integration-tests/presentation/src/main/res/drawable/app_icon_your_company.png b/tv/integration-tests/presentation/src/main/res/drawable/app_icon_your_company.png
deleted file mode 100644
index 0a47b01..0000000
--- a/tv/integration-tests/presentation/src/main/res/drawable/app_icon_your_company.png
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/presentation/src/main/res/layout/activity_main.xml b/tv/integration-tests/presentation/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 2a1b45b..0000000
--- a/tv/integration-tests/presentation/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
- -->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/main_browse_fragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity"
- tools:deviceIds="tv"
- tools:ignore="MergeRootFrame" />
\ No newline at end of file
diff --git a/tv/integration-tests/presentation/src/main/res/mipmap-hdpi/ic_launcher.webp b/tv/integration-tests/presentation/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index c209e78..0000000
--- a/tv/integration-tests/presentation/src/main/res/mipmap-hdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/presentation/src/main/res/mipmap-mdpi/ic_launcher.webp b/tv/integration-tests/presentation/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 4f0f1d6..0000000
--- a/tv/integration-tests/presentation/src/main/res/mipmap-mdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/presentation/src/main/res/mipmap-xhdpi/ic_launcher.webp b/tv/integration-tests/presentation/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 948a307..0000000
--- a/tv/integration-tests/presentation/src/main/res/mipmap-xhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/presentation/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/tv/integration-tests/presentation/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 28d4b77..0000000
--- a/tv/integration-tests/presentation/src/main/res/mipmap-xxhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/tv/integration-tests/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index aa7d642..0000000
--- a/tv/integration-tests/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/presentation/src/main/res/values/colors.xml b/tv/integration-tests/presentation/src/main/res/values/colors.xml
deleted file mode 100644
index 733f3f5..0000000
--- a/tv/integration-tests/presentation/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<resources>
- <color name="background_gradient_start">#000000</color>
- <color name="background_gradient_end">#DDDDDD</color>
- <color name="fastlane_background">#0096a6</color>
- <color name="search_opaque">#ffaa3f</color>
- <color name="selected_background">#ffaa3f</color>
- <color name="default_background">#3d3d3d</color>
-</resources>
\ No newline at end of file
diff --git a/tv/integration-tests/presentation/src/main/res/values/strings.xml b/tv/integration-tests/presentation/src/main/res/values/strings.xml
deleted file mode 100644
index 38b8b29..0000000
--- a/tv/integration-tests/presentation/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<!--
- Copyright 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<resources>
- <string name="app_name">Presentation app</string>
- <string name="browse_title">Presentation app for visitors</string>
- <string name="related_movies">Related Videos</string>
- <string name="grid_view">Grid View</string>
- <string name="error_fragment">Error Fragment</string>
- <string name="personal_settings">Personal Settings</string>
- <string name="watch_trailer_1">Watch trailer</string>
- <string name="watch_trailer_2">FREE</string>
- <string name="rent_1">Rent By Day</string>
- <string name="rent_2">From $1.99</string>
- <string name="buy_1">Buy and Own</string>
- <string name="buy_2">AT $9.99</string>
- <string name="movie">Movie</string>
-
- <!-- Error messages -->
- <string name="error_fragment_message">An error occurred</string>
- <string name="dismiss_error">Dismiss</string>
-</resources>
\ No newline at end of file
diff --git a/tv/integration-tests/presentation/src/main/res/values/themes.xml b/tv/integration-tests/presentation/src/main/res/values/themes.xml
deleted file mode 100644
index 8df2c77..0000000
--- a/tv/integration-tests/presentation/src/main/res/values/themes.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<!--
- Copyright 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<resources>
- <style name="Theme.Androidx" parent="@style/Theme.AppCompat" />
-</resources>
\ No newline at end of file
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ListItem.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ListItem.kt
index ac8a7822..bab1268 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/ListItem.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ListItem.kt
@@ -30,7 +30,6 @@
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
@@ -302,13 +301,19 @@
verticalAlignment = Alignment.CenterVertically
) {
leadingContent?.let {
- Box(
- modifier =
- Modifier.defaultMinSize(minWidth = minIconSize, minHeight = minIconSize)
- .graphicsLayer { alpha = ListItemDefaults.LeadingContentOpacity },
- contentAlignment = Alignment.Center,
- content = it
- )
+ CompositionLocalProvider(
+ LocalContentColor provides ListItemDefaults.LeadingContentColor
+ ) {
+ Box(
+ modifier =
+ Modifier.defaultMinSize(
+ minWidth = minIconSize,
+ minHeight = minIconSize
+ ),
+ contentAlignment = Alignment.Center,
+ content = it
+ )
+ }
Spacer(modifier = Modifier.padding(end = ListItemDefaults.LeadingContentEndPadding))
}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ListItemDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ListItemDefaults.kt
index 74d3731..1a51101 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/ListItemDefaults.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ListItemDefaults.kt
@@ -58,7 +58,9 @@
/** The default content padding [PaddingValues] used by [DenseListItem] */
internal val ContentPaddingDense = PaddingValues(horizontal = 12.dp, vertical = 10.dp)
- internal const val LeadingContentOpacity = 0.8f
+ internal val LeadingContentColor
+ @ReadOnlyComposable @Composable get() = LocalContentColor.current.copy(alpha = 0.8f)
+
internal const val OverlineContentOpacity = 0.6f
internal const val SupportingContentOpacity = 0.8f
diff --git a/versionedparcelable/versionedparcelable-compiler/src/main/java/androidx/versionedparcelable/compiler/VersionedParcelProcessor.java b/versionedparcelable/versionedparcelable-compiler/src/main/java/androidx/versionedparcelable/compiler/VersionedParcelProcessor.java
index cfe94e7..fa4c0e0 100644
--- a/versionedparcelable/versionedparcelable-compiler/src/main/java/androidx/versionedparcelable/compiler/VersionedParcelProcessor.java
+++ b/versionedparcelable/versionedparcelable-compiler/src/main/java/androidx/versionedparcelable/compiler/VersionedParcelProcessor.java
@@ -182,7 +182,7 @@
String jetifyAs = getValue(annotation, "jetifyAs", "");
String factoryClass = getValue(annotation, "factory", "");
parseDeprecated(takenIds, deprecatedIds);
- checkClass(versionedParcelable.asType().toString(), versionedParcelable, takenIds);
+ checkClass(typeString(versionedParcelable.asType()), versionedParcelable, takenIds);
ArrayList<Element> f = new ArrayList<>();
TypeElement te = (TypeElement) mEnv.getTypeUtils().asElement(
@@ -285,7 +285,7 @@
writeBuilder.beginControlFlow("if (!$T.equals($L, obj.$L))",
Arrays.class, strip(defaultValue), e.getSimpleName());
} else {
- String v = "java.lang.String".equals(e.asType().toString()) ? defaultValue
+ String v = "java.lang.String".equals(typeString(e.asType())) ? defaultValue
: strip(defaultValue);
writeBuilder.beginControlFlow("if (!$L.equals(obj.$L))",
v, e.getSimpleName());
@@ -354,6 +354,11 @@
return pkg;
}
+ /** Returns a simple string version of the type, with no annotations. */
+ private String typeString(TypeMirror type) {
+ return TypeName.get(type).toString();
+ }
+
private String getMethod(VariableElement e) {
TypeMirror type = e.asType();
String m = getMethod(type);
@@ -368,7 +373,7 @@
.asElement(te.getSuperclass()) : null;
}
// Manual handling for generic arrays to go last.
- if (type.toString().contains("[]")) {
+ if (typeString(type).contains("[]")) {
return "Array";
}
error("Can't find type for " + e + " (type: " + type + ")");
@@ -376,11 +381,11 @@
}
private boolean isArray(VariableElement e) {
- return e.asType().toString().endsWith("[]");
+ return typeString(e.asType()).endsWith("[]");
}
private boolean isNative(VariableElement e) {
- String type = e.asType().toString();
+ String type = typeString(e.asType());
return "int".equals(type)
|| "byte".equals(type)
|| "char".equals(type)
@@ -392,7 +397,7 @@
private String getMethod(TypeMirror typeMirror) {
// Get an annotation-free version of the type string through TypeName
- String typeString = TypeName.get(typeMirror).toString();
+ String typeString = typeString(typeMirror);
for (Pattern p: mMethodLookup.keySet()) {
if (p.matcher(typeString).find()) {
return mMethodLookup.get(p);
@@ -423,7 +428,7 @@
List<? extends AnnotationMirror> annotations = element.getAnnotationMirrors();
for (i = 0; i < annotations.size(); i++) {
AnnotationMirror annotation = annotations.get(i);
- if (annotation.getAnnotationType().toString().equals(PARCEL_FIELD)) {
+ if (typeString(annotation.getAnnotationType()).equals(PARCEL_FIELD)) {
String valStr = getValue(annotation, "value", null);
if (valStr == null) {
return;
@@ -435,7 +440,7 @@
takenIds.add(valStr);
break;
}
- if (annotation.getAnnotationType().toString().equals(NON_PARCEL_FIELD)) {
+ if (typeString(annotation.getAnnotationType()).equals(NON_PARCEL_FIELD)) {
break;
}
}
@@ -466,7 +471,7 @@
List<? extends AnnotationMirror> annotations = e.getAnnotationMirrors();
for (int i = 0; i < annotations.size(); i++) {
AnnotationMirror annotation = annotations.get(i);
- if (annotation.getAnnotationType().toString().equals(PARCEL_FIELD)) {
+ if (typeString(annotation.getAnnotationType()).equals(PARCEL_FIELD)) {
return annotation;
}
}
diff --git a/wear/compose/compose-foundation/build.gradle b/wear/compose/compose-foundation/build.gradle
index aa8f032..4844a72 100644
--- a/wear/compose/compose-foundation/build.gradle
+++ b/wear/compose/compose-foundation/build.gradle
@@ -32,14 +32,14 @@
}
dependencies {
- api("androidx.compose.foundation:foundation:1.7.0-rc01")
- api("androidx.compose.ui:ui:1.7.0-rc01")
- api("androidx.compose.ui:ui-text:1.7.0-rc01")
- api("androidx.compose.runtime:runtime:1.7.0-rc01")
+ api("androidx.compose.foundation:foundation:1.7.0")
+ api("androidx.compose.ui:ui:1.7.0")
+ api("androidx.compose.ui:ui-text:1.7.0")
+ api("androidx.compose.runtime:runtime:1.7.0")
implementation(libs.kotlinStdlib)
- implementation("androidx.compose.foundation:foundation-layout:1.7.0-rc01")
- implementation("androidx.compose.ui:ui-util:1.7.0-rc01")
+ implementation("androidx.compose.foundation:foundation-layout:1.7.0")
+ implementation("androidx.compose.ui:ui-util:1.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
implementation("androidx.core:core:1.12.0")
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedTextStyle.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedTextStyle.kt
index 659932a..56c924c 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedTextStyle.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedTextStyle.kt
@@ -163,7 +163,8 @@
@Deprecated(
"This overload is provided for backwards compatibility with Compose for " +
- "Wear OS 1.0. A newer overload is available with additional font parameters."
+ "Wear OS 1.0. A newer overload is available with additional font parameters.",
+ level = DeprecationLevel.HIDDEN
)
fun copy(
background: Color = this.background,
@@ -184,7 +185,8 @@
@Deprecated(
"This overload is provided for backwards compatibility with Compose for " +
- "Wear OS 1.4. A newer overload is available with additional letter spacing parameter."
+ "Wear OS 1.4. A newer overload is available with additional letter spacing parameter.",
+ level = DeprecationLevel.HIDDEN
)
fun copy(
background: Color = this.background,
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasureResult.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasureResult.kt
index bfda803..3f316f2 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasureResult.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasureResult.kt
@@ -29,7 +29,7 @@
/** Last known height for the anchor item or negative number if it hasn't been measured. */
val lastMeasuredItemHeight: Int,
/** Layout information for the visible items. */
- val visibleItems: List<LazyColumnVisibleItemInfo>,
+ override val visibleItems: List<LazyColumnVisibleItemInfo>,
/** see [LazyColumnLayoutInfo.totalItemsCount] */
- val totalItemsCount: Int,
-) : MeasureResult by measureResult
+ override val totalItemsCount: Int,
+) : LazyColumnLayoutInfo, MeasureResult by measureResult
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasuredItem.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasuredItem.kt
index fae8d47..796730c 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasuredItem.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasuredItem.kt
@@ -24,22 +24,22 @@
/** Represents a placeable item in the [LazyColumn] layout. */
internal data class LazyColumnMeasuredItem(
/** The index of the item in the list. */
- val index: Int,
+ override val index: Int,
/** The [Placeable] representing the content of the item. */
val placeable: Placeable,
/** The constraints of the container holding the item. */
val containerConstraints: Constraints,
/** The vertical offset of the item from the top of the list after transformations applied. */
- var offset: Int,
+ override var offset: Int,
/** Scroll progress of the item used to calculate transformations applied. */
- val scrollProgress: LazyColumnItemScrollProgress,
+ override val scrollProgress: LazyColumnItemScrollProgress,
/** The horizontal alignment to apply during placement. */
val horizontalAlignment: Alignment.Horizontal,
/** The [LayoutDirection] of the `Layout`. */
private val layoutDirection: LayoutDirection,
-) {
+) : LazyColumnVisibleItemInfo {
/** The height of the item after transformations applied. */
- val height =
+ override val height =
(placeable.parentData as? HeightProviderParentData)?.let {
it.heightProvider(placeable.height, scrollProgress)
} ?: placeable.height
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasurement.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasurement.kt
index f0efba5..0001984 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasurement.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnMeasurement.kt
@@ -29,7 +29,6 @@
import androidx.compose.ui.unit.constrainHeight
import androidx.compose.ui.unit.constrainWidth
import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMap
import kotlin.math.abs
import kotlin.math.roundToInt
@@ -142,15 +141,7 @@
anchorItemIndex = anchorItem.index,
anchorItemScrollOffset =
anchorItem.let { it.offset + it.height - containerConstraints.maxHeight / 2 },
- visibleItems =
- visibleItems.fastMap {
- LazyColumnVisibleItemInfoImpl(
- index = it.index,
- offset = it.offset,
- height = it.height,
- scrollProgress = it.scrollProgress,
- )
- },
+ visibleItems = visibleItems,
totalItemsCount = itemsCount,
lastMeasuredItemHeight = anchorItem.height,
measureResult =
@@ -160,13 +151,6 @@
)
}
-private class LazyColumnVisibleItemInfoImpl(
- override val index: Int,
- override val offset: Int,
- override val height: Int,
- override val scrollProgress: LazyColumnItemScrollProgress
-) : LazyColumnVisibleItemInfo
-
private fun bottomItemScrollProgress(
offset: Int,
height: Int,
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnState.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnState.kt
index 5b71249..09d614c 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnState.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/LazyColumnState.kt
@@ -23,8 +23,11 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.neverEqualPolicy
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.Remeasurement
import androidx.compose.ui.layout.RemeasurementModifier
import kotlin.math.abs
@@ -48,13 +51,19 @@
block: suspend ScrollScope.() -> Unit
) = scrollableState.scroll(scrollPriority, block)
- var layoutInfo: LazyColumnLayoutInfo by mutableStateOf(LazyColumnLayoutInfoImpl(emptyList(), 0))
- private set
+ private val layoutInfoState = mutableStateOf(EmptyLazyColumnMeasureResult, neverEqualPolicy())
- private data class LazyColumnLayoutInfoImpl(
- override val visibleItems: List<LazyColumnVisibleItemInfo>,
- override val totalItemsCount: Int,
- ) : LazyColumnLayoutInfo
+ /**
+ * The object of LazyColumnLayoutInfo calculated during the last layout pass. For example, you
+ * can use it to calculate what items are currently visible. Note that this property is
+ * observable and is updated after every scroll or remeasure. If you use it in the composable
+ * function it will be recomposed on every change causing potential performance issues including
+ * infinity recomposition loop. Therefore, avoid using it in the composition. If you want to run
+ * some side effects like sending an analytics event or updating a state based on this value
+ * consider using "snapshotFlow":
+ */
+ val layoutInfo: LazyColumnLayoutInfo
+ get() = layoutInfoState.value
internal var scrollToBeConsumed = 0f
private set
@@ -85,11 +94,7 @@
anchorItemIndex = measureResult.anchorItemIndex
anchorItemScrollOffset = measureResult.anchorItemScrollOffset
lastMeasuredAnchorItemHeight = measureResult.lastMeasuredItemHeight
- layoutInfo =
- LazyColumnLayoutInfoImpl(
- visibleItems = measureResult.visibleItems,
- totalItemsCount = measureResult.totalItemsCount
- )
+ layoutInfoState.value = measureResult
}
private val scrollableState = ScrollableState { -onScroll(-it) }
@@ -118,3 +123,22 @@
}
}
}
+
+private val EmptyLazyColumnMeasureResult =
+ LazyColumnMeasureResult(
+ anchorItemIndex = 0,
+ anchorItemScrollOffset = 0,
+ visibleItems = emptyList(),
+ totalItemsCount = 0,
+ lastMeasuredItemHeight = Int.MIN_VALUE,
+ measureResult =
+ object : MeasureResult {
+ override val width: Int = 0
+ override val height: Int = 0
+
+ @Suppress("PrimitiveInCollection")
+ override val alignmentLines: Map<AlignmentLine, Int> = emptyMap()
+
+ override fun placeChildren() {}
+ }
+ )
diff --git a/wear/compose/compose-material-core/build.gradle b/wear/compose/compose-material-core/build.gradle
index e380531..df1b6c6 100644
--- a/wear/compose/compose-material-core/build.gradle
+++ b/wear/compose/compose-material-core/build.gradle
@@ -33,16 +33,16 @@
}
dependencies {
- api("androidx.compose.foundation:foundation:1.7.0-rc01")
- api("androidx.compose.ui:ui:1.7.0-rc01")
- api("androidx.compose.ui:ui-text:1.7.0-rc01")
- api("androidx.compose.runtime:runtime:1.7.0-rc01")
+ api("androidx.compose.foundation:foundation:1.7.0")
+ api("androidx.compose.ui:ui:1.7.0")
+ api("androidx.compose.ui:ui-text:1.7.0")
+ api("androidx.compose.runtime:runtime:1.7.0")
implementation(libs.kotlinStdlib)
- implementation("androidx.compose.animation:animation:1.7.0-rc01")
- implementation("androidx.compose.material:material-icons-core:1.7.0-rc01")
- implementation("androidx.compose.material:material-ripple:1.7.0-rc01")
- implementation("androidx.compose.ui:ui-util:1.7.0-rc01")
+ implementation("androidx.compose.animation:animation:1.7.0")
+ implementation("androidx.compose.material:material-icons-core:1.7.0")
+ implementation("androidx.compose.material:material-ripple:1.7.0")
+ implementation("androidx.compose.ui:ui-util:1.7.0")
implementation(project(":wear:compose:compose-foundation"))
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Resources.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Resources.kt
index 1a967f1..7b9faf1 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Resources.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Resources.kt
@@ -62,11 +62,11 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Composable
-fun screenHeightDp() = LocalContext.current.resources.configuration.screenHeightDp
+fun screenHeightDp() = LocalConfiguration.current.screenHeightDp
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Composable
-fun screenWidthDp() = LocalContext.current.resources.configuration.screenWidthDp
+fun screenWidthDp() = LocalConfiguration.current.screenWidthDp
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Composable
diff --git a/wear/compose/compose-material/build.gradle b/wear/compose/compose-material/build.gradle
index 864af0d..1d2fc79 100644
--- a/wear/compose/compose-material/build.gradle
+++ b/wear/compose/compose-material/build.gradle
@@ -31,17 +31,17 @@
}
dependencies {
- api("androidx.compose.foundation:foundation:1.7.0-rc01")
- api("androidx.compose.ui:ui:1.7.0-rc01")
- api("androidx.compose.ui:ui-text:1.7.0-rc01")
- api("androidx.compose.runtime:runtime:1.7.0-rc01")
+ api("androidx.compose.foundation:foundation:1.7.0")
+ api("androidx.compose.ui:ui:1.7.0")
+ api("androidx.compose.ui:ui-text:1.7.0")
+ api("androidx.compose.runtime:runtime:1.7.0")
api(project(":wear:compose:compose-foundation"))
implementation(libs.kotlinStdlib)
- implementation("androidx.compose.animation:animation:1.7.0-rc01")
- implementation("androidx.compose.material:material-icons-core:1.7.0-rc01")
- implementation("androidx.compose.material:material-ripple:1.7.0-rc01")
- implementation("androidx.compose.ui:ui-util:1.7.0-rc01")
+ implementation("androidx.compose.animation:animation:1.7.0")
+ implementation("androidx.compose.material:material-icons-core:1.7.0")
+ implementation("androidx.compose.material:material-ripple:1.7.0")
+ implementation("androidx.compose.ui:ui-util:1.7.0")
implementation(project(":wear:compose:compose-material-core"))
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
implementation("androidx.lifecycle:lifecycle-common:2.7.0")
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index d1d99cf..b0b63b9 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -1,6 +1,28 @@
// Signature format: 4.0
package androidx.wear.compose.material3 {
+ public final class AlertDialogDefaults {
+ method @androidx.compose.runtime.Composable public void BottomButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public void ConfirmButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public void DismissButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public void GroupSeparator();
+ method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues contentPadding(boolean hasBottomButton);
+ method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getConfirmIcon();
+ method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getDismissIcon();
+ method public float getEdgeButtonExtraTopPadding();
+ method public androidx.compose.foundation.layout.Arrangement.Vertical getVerticalArrangement();
+ property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> ConfirmIcon;
+ property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> DismissIcon;
+ property public final androidx.compose.foundation.layout.Arrangement.Vertical VerticalArrangement;
+ property public final float edgeButtonExtraTopPadding;
+ field public static final androidx.wear.compose.material3.AlertDialogDefaults INSTANCE;
+ }
+
+ public final class AlertDialogKt {
+ method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
+ method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> confirmButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissButton, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
+ }
+
@RequiresApi(31) public final class AnimatedTextDefaults {
field public static final int CacheSize = 5; // 0x5
field public static final androidx.wear.compose.material3.AnimatedTextDefaults INSTANCE;
@@ -298,12 +320,52 @@
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static long contentColorFor(long backgroundColor);
}
+ public final class ConfirmationColors {
+ ctor public ConfirmationColors(long iconColor, long iconContainerColor, long textColor);
+ method public long getIconColor();
+ method public long getIconContainerColor();
+ method public long getTextColor();
+ property public final long iconColor;
+ property public final long iconContainerColor;
+ property public final long textColor;
+ }
+
+ public final class ConfirmationDefaults {
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors confirmationColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors confirmationColors(optional long iconColor, optional long iconContainerColor, optional long textColor);
+ method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> curvedText(String text, optional androidx.wear.compose.foundation.CurvedTextStyle style);
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors failureColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors failureColors(optional long iconColor, optional long iconContainerColor, optional long textColor);
+ method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> failureText();
+ method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getFailureIcon();
+ method public float getIconSize();
+ method public float getSmallIconSize();
+ method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getSuccessIcon();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors successColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors successColors(optional long iconColor, optional long iconContainerColor, optional long textColor);
+ method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> successText();
+ property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> FailureIcon;
+ property public final float IconSize;
+ property public final float SmallIconSize;
+ property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> SuccessIcon;
+ field public static final long ConfirmationDurationMillis = 4000L; // 0xfa0L
+ field public static final androidx.wear.compose.material3.ConfirmationDefaults INSTANCE;
+ }
+
+ public final class ConfirmationKt {
+ method @androidx.compose.runtime.Composable public static void Confirmation(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? text, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.ConfirmationColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void Confirmation(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.ConfirmationColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void FailureConfirmation(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.wear.compose.material3.ConfirmationColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void SuccessConfirmation(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.wear.compose.material3.ConfirmationColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ }
+
public final class ContentColorKt {
method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> getLocalContentColor();
property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
}
public final class CurvedTextDefaults {
+ method @androidx.compose.runtime.Composable public long backgroundColor();
field public static final androidx.wear.compose.material3.CurvedTextDefaults INSTANCE;
field public static final float ScrollableContentMaxSweepAngle = 70.0f;
field public static final float StaticContentMaxSweepAngle = 120.0f;
@@ -313,6 +375,49 @@
method public static void curvedText(androidx.wear.compose.foundation.CurvedScope, String text, optional androidx.wear.compose.foundation.CurvedModifier modifier, optional float maxSweepAngle, optional long background, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.wear.compose.foundation.CurvedTextStyle? style, optional androidx.wear.compose.foundation.CurvedDirection.Angular? angularDirection, optional int overflow);
}
+ @androidx.compose.runtime.Immutable public final class DatePickerColors {
+ ctor public DatePickerColors(long selectedPickerContentColor, long unselectedPickerContentColor, long pickerLabelColor, long nextButtonContentColor, long nextButtonContainerColor, long confirmButtonContentColor, long confirmButtonContainerColor);
+ method public long getConfirmButtonContainerColor();
+ method public long getConfirmButtonContentColor();
+ method public long getNextButtonContainerColor();
+ method public long getNextButtonContentColor();
+ method public long getPickerLabelColor();
+ method public long getSelectedPickerContentColor();
+ method public long getUnselectedPickerContentColor();
+ property public final long confirmButtonContainerColor;
+ property public final long confirmButtonContentColor;
+ property public final long nextButtonContainerColor;
+ property public final long nextButtonContentColor;
+ property public final long pickerLabelColor;
+ property public final long selectedPickerContentColor;
+ property public final long unselectedPickerContentColor;
+ }
+
+ public final class DatePickerDefaults {
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.DatePickerColors datePickerColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.DatePickerColors datePickerColors(optional long selectedPickerContentColor, optional long unselectedPickerContentColor, optional long pickerLabelColor, optional long nextButtonContentColor, optional long nextButtonContainerColor, optional long confirmButtonContentColor, optional long confirmButtonContainerColor);
+ method @androidx.compose.runtime.Composable public int getDatePickerType();
+ property @androidx.compose.runtime.Composable public final int datePickerType;
+ field public static final androidx.wear.compose.material3.DatePickerDefaults INSTANCE;
+ }
+
+ public final class DatePickerKt {
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.compose.runtime.Composable public static void DatePicker(java.time.LocalDate initialDate, kotlin.jvm.functions.Function1<? super java.time.LocalDate,kotlin.Unit> onDatePicked, optional androidx.compose.ui.Modifier modifier, optional java.time.LocalDate? minDate, optional java.time.LocalDate? maxDate, optional int datePickerType, optional androidx.wear.compose.material3.DatePickerColors colors);
+ }
+
+ @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class DatePickerType {
+ field public static final androidx.wear.compose.material3.DatePickerType.Companion Companion;
+ }
+
+ public static final class DatePickerType.Companion {
+ method public int getDayMonthYear();
+ method public int getMonthDayYear();
+ method public int getYearMonthDay();
+ property public final int DayMonthYear;
+ property public final int MonthDayYear;
+ property public final int YearMonthDay;
+ }
+
public final class EdgeButtonKt {
method @androidx.compose.runtime.Composable public static void EdgeButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional float buttonHeight, optional boolean enabled, optional androidx.wear.compose.material3.ButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
}
@@ -346,6 +451,7 @@
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors filledVariantIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method public float getDefaultButtonSize();
method public float getDefaultIconSize();
+ method public float getDisabledImageOpacity();
method public float getExtraSmallButtonSize();
method public float getLargeButtonSize();
method public float getLargeIconSize();
@@ -356,8 +462,8 @@
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors iconButtonColors();
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method public float iconSizeFor(float size);
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ToggleButtonColors iconToggleButtonColors();
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ToggleButtonColors iconToggleButtonColors(optional long checkedContainerColor, optional long checkedContentColor, optional long uncheckedContainerColor, optional long uncheckedContentColor, optional long disabledCheckedContainerColor, optional long disabledCheckedContentColor, optional long disabledUncheckedContainerColor, optional long disabledUncheckedContentColor);
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconToggleButtonColors iconToggleButtonColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconToggleButtonColors iconToggleButtonColors(optional long checkedContainerColor, optional long checkedContentColor, optional long uncheckedContainerColor, optional long uncheckedContentColor, optional long disabledCheckedContainerColor, optional long disabledCheckedContentColor, optional long disabledUncheckedContainerColor, optional long disabledUncheckedContentColor);
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors outlinedIconButtonColors();
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors outlinedIconButtonColors(optional long contentColor, optional long disabledContentColor);
property public final float DefaultButtonSize;
@@ -367,6 +473,7 @@
property public final float LargeIconSize;
property public final float SmallButtonSize;
property public final float SmallIconSize;
+ property public final float disabledImageOpacity;
property @androidx.compose.runtime.Composable public final androidx.compose.foundation.shape.CornerBasedShape pressedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.foundation.shape.RoundedCornerShape shape;
field public static final androidx.wear.compose.material3.IconButtonDefaults INSTANCE;
@@ -376,7 +483,7 @@
method @androidx.compose.runtime.Composable public static void FilledIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional String? onLongClickLabel, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void FilledTonalIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional String? onLongClickLabel, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional String? onLongClickLabel, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.wear.compose.material3.ToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.wear.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void OutlinedIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional String? onLongClickLabel, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
}
@@ -386,6 +493,26 @@
method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
}
+ @androidx.compose.runtime.Immutable public final class IconToggleButtonColors {
+ ctor public IconToggleButtonColors(long checkedContainerColor, long checkedContentColor, long uncheckedContainerColor, long uncheckedContentColor, long disabledCheckedContainerColor, long disabledCheckedContentColor, long disabledUncheckedContainerColor, long disabledUncheckedContentColor);
+ method public long getCheckedContainerColor();
+ method public long getCheckedContentColor();
+ method public long getDisabledCheckedContainerColor();
+ method public long getDisabledCheckedContentColor();
+ method public long getDisabledUncheckedContainerColor();
+ method public long getDisabledUncheckedContentColor();
+ method public long getUncheckedContainerColor();
+ method public long getUncheckedContentColor();
+ property public final long checkedContainerColor;
+ property public final long checkedContentColor;
+ property public final long disabledCheckedContainerColor;
+ property public final long disabledCheckedContentColor;
+ property public final long disabledUncheckedContainerColor;
+ property public final long disabledUncheckedContentColor;
+ property public final long uncheckedContainerColor;
+ property public final long uncheckedContentColor;
+ }
+
@SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.wear.compose.material3.ExperimentalWearMaterial3Api public final class InlineSliderColors {
ctor public InlineSliderColors(long containerColor, long buttonIconColor, long selectedBarColor, long unselectedBarColor, long barSeparatorColor, long disabledContainerColor, long disabledButtonIconColor, long disabledSelectedBarColor, long disabledUnselectedBarColor, long disabledBarSeparatorColor);
method public long getBarSeparatorColor();
@@ -502,6 +629,34 @@
method public static androidx.wear.compose.material3.MotionScheme standardMotionScheme();
}
+ public final class OpenOnPhoneDialogColors {
+ ctor public OpenOnPhoneDialogColors(long iconColor, long iconContainerColor, long progressIndicatorColor, long progressTrackColor, long textColor);
+ method public long getIconColor();
+ method public long getIconContainerColor();
+ method public long getProgressIndicatorColor();
+ method public long getProgressTrackColor();
+ method public long getTextColor();
+ property public final long iconColor;
+ property public final long iconContainerColor;
+ property public final long progressIndicatorColor;
+ property public final long progressTrackColor;
+ property public final long textColor;
+ }
+
+ public final class OpenOnPhoneDialogDefaults {
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors(optional long iconColor, optional long iconContainerColor, optional long progressIndicatorColor, optional long progressTrackColor, optional long textColor);
+ method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> curvedText(optional String text, optional androidx.wear.compose.foundation.CurvedTextStyle style);
+ method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getIcon();
+ property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> Icon;
+ field public static final long DurationMillis = 4000L; // 0xfa0L
+ field public static final androidx.wear.compose.material3.OpenOnPhoneDialogDefaults INSTANCE;
+ }
+
+ public final class OpenOnPhoneDialogKt {
+ method @androidx.compose.runtime.Composable public static void OpenOnPhoneDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.wear.compose.material3.OpenOnPhoneDialogColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ }
+
public final class PickerDefaults {
method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior flingBehavior(androidx.wear.compose.material3.PickerState state, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
method public float getGradientRatio();
@@ -1073,8 +1228,8 @@
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TextButtonColors outlinedTextButtonColors(optional long contentColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TextButtonColors textButtonColors();
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TextButtonColors textButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ToggleButtonColors textToggleButtonColors();
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ToggleButtonColors textToggleButtonColors(optional long checkedContainerColor, optional long checkedContentColor, optional long uncheckedContainerColor, optional long uncheckedContentColor, optional long disabledCheckedContainerColor, optional long disabledCheckedContentColor, optional long disabledUncheckedContainerColor, optional long disabledUncheckedContentColor);
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TextToggleButtonColors textToggleButtonColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TextToggleButtonColors textToggleButtonColors(optional long checkedContainerColor, optional long checkedContentColor, optional long uncheckedContainerColor, optional long uncheckedContentColor, optional long disabledCheckedContainerColor, optional long disabledCheckedContentColor, optional long disabledUncheckedContainerColor, optional long disabledUncheckedContentColor);
property public final float DefaultButtonSize;
property public final float LargeButtonSize;
property public final float SmallButtonSize;
@@ -1088,7 +1243,7 @@
public final class TextButtonKt {
method @androidx.compose.runtime.Composable public static void TextButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional String? onLongClickLabel, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.TextButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public static void TextToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.wear.compose.material3.ToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void TextToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.wear.compose.material3.TextToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
}
public final class TextKt {
@@ -1105,6 +1260,26 @@
property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
}
+ @androidx.compose.runtime.Immutable public final class TextToggleButtonColors {
+ ctor public TextToggleButtonColors(long checkedContainerColor, long checkedContentColor, long uncheckedContainerColor, long uncheckedContentColor, long disabledCheckedContainerColor, long disabledCheckedContentColor, long disabledUncheckedContainerColor, long disabledUncheckedContentColor);
+ method public long getCheckedContainerColor();
+ method public long getCheckedContentColor();
+ method public long getDisabledCheckedContainerColor();
+ method public long getDisabledCheckedContentColor();
+ method public long getDisabledUncheckedContainerColor();
+ method public long getDisabledUncheckedContentColor();
+ method public long getUncheckedContainerColor();
+ method public long getUncheckedContentColor();
+ property public final long checkedContainerColor;
+ property public final long checkedContentColor;
+ property public final long disabledCheckedContainerColor;
+ property public final long disabledCheckedContentColor;
+ property public final long disabledUncheckedContainerColor;
+ property public final long disabledUncheckedContentColor;
+ property public final long uncheckedContainerColor;
+ property public final long uncheckedContentColor;
+ }
+
@androidx.compose.runtime.Immutable public final class TimePickerColors {
ctor public TimePickerColors(long selectedPickerContentColor, long unselectedPickerContentColor, long separatorColor, long pickerLabelColor, long confirmButtonContentColor, long confirmButtonContainerColor);
method public long getConfirmButtonContainerColor();
@@ -1175,26 +1350,6 @@
method public abstract void time();
}
- @androidx.compose.runtime.Immutable public final class ToggleButtonColors {
- ctor public ToggleButtonColors(long checkedContainerColor, long checkedContentColor, long uncheckedContainerColor, long uncheckedContentColor, long disabledCheckedContainerColor, long disabledCheckedContentColor, long disabledUncheckedContainerColor, long disabledUncheckedContentColor);
- method public long getCheckedContainerColor();
- method public long getCheckedContentColor();
- method public long getDisabledCheckedContainerColor();
- method public long getDisabledCheckedContentColor();
- method public long getDisabledUncheckedContainerColor();
- method public long getDisabledUncheckedContentColor();
- method public long getUncheckedContainerColor();
- method public long getUncheckedContentColor();
- property public final long checkedContainerColor;
- property public final long checkedContentColor;
- property public final long disabledCheckedContainerColor;
- property public final long disabledCheckedContentColor;
- property public final long disabledUncheckedContainerColor;
- property public final long disabledUncheckedContentColor;
- property public final long uncheckedContainerColor;
- property public final long uncheckedContentColor;
- }
-
public fun interface TouchExplorationStateProvider {
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<java.lang.Boolean> touchExplorationState();
}
@@ -1250,32 +1405,6 @@
}
-package androidx.wear.compose.material3.dialog {
-
- public final class AlertDialogDefaults {
- method @androidx.compose.runtime.Composable public void BottomButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public void ConfirmButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public void DismissButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public void GroupSeparator();
- method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues contentPadding(boolean hasBottomButton);
- method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getConfirmIcon();
- method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getDismissIcon();
- method public float getEdgeButtonExtraTopPadding();
- method public androidx.compose.foundation.layout.Arrangement.Vertical getVerticalArrangement();
- property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> ConfirmIcon;
- property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> DismissIcon;
- property public final androidx.compose.foundation.layout.Arrangement.Vertical VerticalArrangement;
- property public final float edgeButtonExtraTopPadding;
- field public static final androidx.wear.compose.material3.dialog.AlertDialogDefaults INSTANCE;
- }
-
- public final class AlertDialogKt {
- method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
- method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> confirmButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissButton, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
- }
-
-}
-
package androidx.wear.compose.material3.lazy {
public final class LazyColumnScrollTransformModifiersKt {
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index d1d99cf..b0b63b9 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -1,6 +1,28 @@
// Signature format: 4.0
package androidx.wear.compose.material3 {
+ public final class AlertDialogDefaults {
+ method @androidx.compose.runtime.Composable public void BottomButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public void ConfirmButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public void DismissButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public void GroupSeparator();
+ method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues contentPadding(boolean hasBottomButton);
+ method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getConfirmIcon();
+ method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getDismissIcon();
+ method public float getEdgeButtonExtraTopPadding();
+ method public androidx.compose.foundation.layout.Arrangement.Vertical getVerticalArrangement();
+ property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> ConfirmIcon;
+ property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> DismissIcon;
+ property public final androidx.compose.foundation.layout.Arrangement.Vertical VerticalArrangement;
+ property public final float edgeButtonExtraTopPadding;
+ field public static final androidx.wear.compose.material3.AlertDialogDefaults INSTANCE;
+ }
+
+ public final class AlertDialogKt {
+ method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
+ method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> confirmButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissButton, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
+ }
+
@RequiresApi(31) public final class AnimatedTextDefaults {
field public static final int CacheSize = 5; // 0x5
field public static final androidx.wear.compose.material3.AnimatedTextDefaults INSTANCE;
@@ -298,12 +320,52 @@
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static long contentColorFor(long backgroundColor);
}
+ public final class ConfirmationColors {
+ ctor public ConfirmationColors(long iconColor, long iconContainerColor, long textColor);
+ method public long getIconColor();
+ method public long getIconContainerColor();
+ method public long getTextColor();
+ property public final long iconColor;
+ property public final long iconContainerColor;
+ property public final long textColor;
+ }
+
+ public final class ConfirmationDefaults {
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors confirmationColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors confirmationColors(optional long iconColor, optional long iconContainerColor, optional long textColor);
+ method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> curvedText(String text, optional androidx.wear.compose.foundation.CurvedTextStyle style);
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors failureColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors failureColors(optional long iconColor, optional long iconContainerColor, optional long textColor);
+ method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> failureText();
+ method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getFailureIcon();
+ method public float getIconSize();
+ method public float getSmallIconSize();
+ method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getSuccessIcon();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors successColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ConfirmationColors successColors(optional long iconColor, optional long iconContainerColor, optional long textColor);
+ method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> successText();
+ property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> FailureIcon;
+ property public final float IconSize;
+ property public final float SmallIconSize;
+ property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> SuccessIcon;
+ field public static final long ConfirmationDurationMillis = 4000L; // 0xfa0L
+ field public static final androidx.wear.compose.material3.ConfirmationDefaults INSTANCE;
+ }
+
+ public final class ConfirmationKt {
+ method @androidx.compose.runtime.Composable public static void Confirmation(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? text, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.ConfirmationColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void Confirmation(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.ConfirmationColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void FailureConfirmation(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.wear.compose.material3.ConfirmationColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void SuccessConfirmation(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.wear.compose.material3.ConfirmationColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ }
+
public final class ContentColorKt {
method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> getLocalContentColor();
property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
}
public final class CurvedTextDefaults {
+ method @androidx.compose.runtime.Composable public long backgroundColor();
field public static final androidx.wear.compose.material3.CurvedTextDefaults INSTANCE;
field public static final float ScrollableContentMaxSweepAngle = 70.0f;
field public static final float StaticContentMaxSweepAngle = 120.0f;
@@ -313,6 +375,49 @@
method public static void curvedText(androidx.wear.compose.foundation.CurvedScope, String text, optional androidx.wear.compose.foundation.CurvedModifier modifier, optional float maxSweepAngle, optional long background, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.wear.compose.foundation.CurvedTextStyle? style, optional androidx.wear.compose.foundation.CurvedDirection.Angular? angularDirection, optional int overflow);
}
+ @androidx.compose.runtime.Immutable public final class DatePickerColors {
+ ctor public DatePickerColors(long selectedPickerContentColor, long unselectedPickerContentColor, long pickerLabelColor, long nextButtonContentColor, long nextButtonContainerColor, long confirmButtonContentColor, long confirmButtonContainerColor);
+ method public long getConfirmButtonContainerColor();
+ method public long getConfirmButtonContentColor();
+ method public long getNextButtonContainerColor();
+ method public long getNextButtonContentColor();
+ method public long getPickerLabelColor();
+ method public long getSelectedPickerContentColor();
+ method public long getUnselectedPickerContentColor();
+ property public final long confirmButtonContainerColor;
+ property public final long confirmButtonContentColor;
+ property public final long nextButtonContainerColor;
+ property public final long nextButtonContentColor;
+ property public final long pickerLabelColor;
+ property public final long selectedPickerContentColor;
+ property public final long unselectedPickerContentColor;
+ }
+
+ public final class DatePickerDefaults {
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.DatePickerColors datePickerColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.DatePickerColors datePickerColors(optional long selectedPickerContentColor, optional long unselectedPickerContentColor, optional long pickerLabelColor, optional long nextButtonContentColor, optional long nextButtonContainerColor, optional long confirmButtonContentColor, optional long confirmButtonContainerColor);
+ method @androidx.compose.runtime.Composable public int getDatePickerType();
+ property @androidx.compose.runtime.Composable public final int datePickerType;
+ field public static final androidx.wear.compose.material3.DatePickerDefaults INSTANCE;
+ }
+
+ public final class DatePickerKt {
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.compose.runtime.Composable public static void DatePicker(java.time.LocalDate initialDate, kotlin.jvm.functions.Function1<? super java.time.LocalDate,kotlin.Unit> onDatePicked, optional androidx.compose.ui.Modifier modifier, optional java.time.LocalDate? minDate, optional java.time.LocalDate? maxDate, optional int datePickerType, optional androidx.wear.compose.material3.DatePickerColors colors);
+ }
+
+ @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class DatePickerType {
+ field public static final androidx.wear.compose.material3.DatePickerType.Companion Companion;
+ }
+
+ public static final class DatePickerType.Companion {
+ method public int getDayMonthYear();
+ method public int getMonthDayYear();
+ method public int getYearMonthDay();
+ property public final int DayMonthYear;
+ property public final int MonthDayYear;
+ property public final int YearMonthDay;
+ }
+
public final class EdgeButtonKt {
method @androidx.compose.runtime.Composable public static void EdgeButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional float buttonHeight, optional boolean enabled, optional androidx.wear.compose.material3.ButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
}
@@ -346,6 +451,7 @@
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors filledVariantIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method public float getDefaultButtonSize();
method public float getDefaultIconSize();
+ method public float getDisabledImageOpacity();
method public float getExtraSmallButtonSize();
method public float getLargeButtonSize();
method public float getLargeIconSize();
@@ -356,8 +462,8 @@
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors iconButtonColors();
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method public float iconSizeFor(float size);
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ToggleButtonColors iconToggleButtonColors();
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ToggleButtonColors iconToggleButtonColors(optional long checkedContainerColor, optional long checkedContentColor, optional long uncheckedContainerColor, optional long uncheckedContentColor, optional long disabledCheckedContainerColor, optional long disabledCheckedContentColor, optional long disabledUncheckedContainerColor, optional long disabledUncheckedContentColor);
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconToggleButtonColors iconToggleButtonColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconToggleButtonColors iconToggleButtonColors(optional long checkedContainerColor, optional long checkedContentColor, optional long uncheckedContainerColor, optional long uncheckedContentColor, optional long disabledCheckedContainerColor, optional long disabledCheckedContentColor, optional long disabledUncheckedContainerColor, optional long disabledUncheckedContentColor);
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors outlinedIconButtonColors();
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors outlinedIconButtonColors(optional long contentColor, optional long disabledContentColor);
property public final float DefaultButtonSize;
@@ -367,6 +473,7 @@
property public final float LargeIconSize;
property public final float SmallButtonSize;
property public final float SmallIconSize;
+ property public final float disabledImageOpacity;
property @androidx.compose.runtime.Composable public final androidx.compose.foundation.shape.CornerBasedShape pressedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.foundation.shape.RoundedCornerShape shape;
field public static final androidx.wear.compose.material3.IconButtonDefaults INSTANCE;
@@ -376,7 +483,7 @@
method @androidx.compose.runtime.Composable public static void FilledIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional String? onLongClickLabel, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void FilledTonalIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional String? onLongClickLabel, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional String? onLongClickLabel, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.wear.compose.material3.ToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.wear.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void OutlinedIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional String? onLongClickLabel, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
}
@@ -386,6 +493,26 @@
method @androidx.compose.runtime.Composable public static void Icon(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
}
+ @androidx.compose.runtime.Immutable public final class IconToggleButtonColors {
+ ctor public IconToggleButtonColors(long checkedContainerColor, long checkedContentColor, long uncheckedContainerColor, long uncheckedContentColor, long disabledCheckedContainerColor, long disabledCheckedContentColor, long disabledUncheckedContainerColor, long disabledUncheckedContentColor);
+ method public long getCheckedContainerColor();
+ method public long getCheckedContentColor();
+ method public long getDisabledCheckedContainerColor();
+ method public long getDisabledCheckedContentColor();
+ method public long getDisabledUncheckedContainerColor();
+ method public long getDisabledUncheckedContentColor();
+ method public long getUncheckedContainerColor();
+ method public long getUncheckedContentColor();
+ property public final long checkedContainerColor;
+ property public final long checkedContentColor;
+ property public final long disabledCheckedContainerColor;
+ property public final long disabledCheckedContentColor;
+ property public final long disabledUncheckedContainerColor;
+ property public final long disabledUncheckedContentColor;
+ property public final long uncheckedContainerColor;
+ property public final long uncheckedContentColor;
+ }
+
@SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.wear.compose.material3.ExperimentalWearMaterial3Api public final class InlineSliderColors {
ctor public InlineSliderColors(long containerColor, long buttonIconColor, long selectedBarColor, long unselectedBarColor, long barSeparatorColor, long disabledContainerColor, long disabledButtonIconColor, long disabledSelectedBarColor, long disabledUnselectedBarColor, long disabledBarSeparatorColor);
method public long getBarSeparatorColor();
@@ -502,6 +629,34 @@
method public static androidx.wear.compose.material3.MotionScheme standardMotionScheme();
}
+ public final class OpenOnPhoneDialogColors {
+ ctor public OpenOnPhoneDialogColors(long iconColor, long iconContainerColor, long progressIndicatorColor, long progressTrackColor, long textColor);
+ method public long getIconColor();
+ method public long getIconContainerColor();
+ method public long getProgressIndicatorColor();
+ method public long getProgressTrackColor();
+ method public long getTextColor();
+ property public final long iconColor;
+ property public final long iconContainerColor;
+ property public final long progressIndicatorColor;
+ property public final long progressTrackColor;
+ property public final long textColor;
+ }
+
+ public final class OpenOnPhoneDialogDefaults {
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors(optional long iconColor, optional long iconContainerColor, optional long progressIndicatorColor, optional long progressTrackColor, optional long textColor);
+ method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> curvedText(optional String text, optional androidx.wear.compose.foundation.CurvedTextStyle style);
+ method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getIcon();
+ property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> Icon;
+ field public static final long DurationMillis = 4000L; // 0xfa0L
+ field public static final androidx.wear.compose.material3.OpenOnPhoneDialogDefaults INSTANCE;
+ }
+
+ public final class OpenOnPhoneDialogKt {
+ method @androidx.compose.runtime.Composable public static void OpenOnPhoneDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.wear.compose.material3.OpenOnPhoneDialogColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ }
+
public final class PickerDefaults {
method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior flingBehavior(androidx.wear.compose.material3.PickerState state, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
method public float getGradientRatio();
@@ -1073,8 +1228,8 @@
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TextButtonColors outlinedTextButtonColors(optional long contentColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TextButtonColors textButtonColors();
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TextButtonColors textButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ToggleButtonColors textToggleButtonColors();
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ToggleButtonColors textToggleButtonColors(optional long checkedContainerColor, optional long checkedContentColor, optional long uncheckedContainerColor, optional long uncheckedContentColor, optional long disabledCheckedContainerColor, optional long disabledCheckedContentColor, optional long disabledUncheckedContainerColor, optional long disabledUncheckedContentColor);
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TextToggleButtonColors textToggleButtonColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TextToggleButtonColors textToggleButtonColors(optional long checkedContainerColor, optional long checkedContentColor, optional long uncheckedContainerColor, optional long uncheckedContentColor, optional long disabledCheckedContainerColor, optional long disabledCheckedContentColor, optional long disabledUncheckedContainerColor, optional long disabledUncheckedContentColor);
property public final float DefaultButtonSize;
property public final float LargeButtonSize;
property public final float SmallButtonSize;
@@ -1088,7 +1243,7 @@
public final class TextButtonKt {
method @androidx.compose.runtime.Composable public static void TextButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional String? onLongClickLabel, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.TextButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public static void TextToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.wear.compose.material3.ToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void TextToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.wear.compose.material3.TextToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.BorderStroke? border, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
}
public final class TextKt {
@@ -1105,6 +1260,26 @@
property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
}
+ @androidx.compose.runtime.Immutable public final class TextToggleButtonColors {
+ ctor public TextToggleButtonColors(long checkedContainerColor, long checkedContentColor, long uncheckedContainerColor, long uncheckedContentColor, long disabledCheckedContainerColor, long disabledCheckedContentColor, long disabledUncheckedContainerColor, long disabledUncheckedContentColor);
+ method public long getCheckedContainerColor();
+ method public long getCheckedContentColor();
+ method public long getDisabledCheckedContainerColor();
+ method public long getDisabledCheckedContentColor();
+ method public long getDisabledUncheckedContainerColor();
+ method public long getDisabledUncheckedContentColor();
+ method public long getUncheckedContainerColor();
+ method public long getUncheckedContentColor();
+ property public final long checkedContainerColor;
+ property public final long checkedContentColor;
+ property public final long disabledCheckedContainerColor;
+ property public final long disabledCheckedContentColor;
+ property public final long disabledUncheckedContainerColor;
+ property public final long disabledUncheckedContentColor;
+ property public final long uncheckedContainerColor;
+ property public final long uncheckedContentColor;
+ }
+
@androidx.compose.runtime.Immutable public final class TimePickerColors {
ctor public TimePickerColors(long selectedPickerContentColor, long unselectedPickerContentColor, long separatorColor, long pickerLabelColor, long confirmButtonContentColor, long confirmButtonContainerColor);
method public long getConfirmButtonContainerColor();
@@ -1175,26 +1350,6 @@
method public abstract void time();
}
- @androidx.compose.runtime.Immutable public final class ToggleButtonColors {
- ctor public ToggleButtonColors(long checkedContainerColor, long checkedContentColor, long uncheckedContainerColor, long uncheckedContentColor, long disabledCheckedContainerColor, long disabledCheckedContentColor, long disabledUncheckedContainerColor, long disabledUncheckedContentColor);
- method public long getCheckedContainerColor();
- method public long getCheckedContentColor();
- method public long getDisabledCheckedContainerColor();
- method public long getDisabledCheckedContentColor();
- method public long getDisabledUncheckedContainerColor();
- method public long getDisabledUncheckedContentColor();
- method public long getUncheckedContainerColor();
- method public long getUncheckedContentColor();
- property public final long checkedContainerColor;
- property public final long checkedContentColor;
- property public final long disabledCheckedContainerColor;
- property public final long disabledCheckedContentColor;
- property public final long disabledUncheckedContainerColor;
- property public final long disabledUncheckedContentColor;
- property public final long uncheckedContainerColor;
- property public final long uncheckedContentColor;
- }
-
public fun interface TouchExplorationStateProvider {
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<java.lang.Boolean> touchExplorationState();
}
@@ -1250,32 +1405,6 @@
}
-package androidx.wear.compose.material3.dialog {
-
- public final class AlertDialogDefaults {
- method @androidx.compose.runtime.Composable public void BottomButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public void ConfirmButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public void DismissButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public void GroupSeparator();
- method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues contentPadding(boolean hasBottomButton);
- method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getConfirmIcon();
- method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getDismissIcon();
- method public float getEdgeButtonExtraTopPadding();
- method public androidx.compose.foundation.layout.Arrangement.Vertical getVerticalArrangement();
- property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> ConfirmIcon;
- property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> DismissIcon;
- property public final androidx.compose.foundation.layout.Arrangement.Vertical VerticalArrangement;
- property public final float edgeButtonExtraTopPadding;
- field public static final androidx.wear.compose.material3.dialog.AlertDialogDefaults INSTANCE;
- }
-
- public final class AlertDialogKt {
- method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
- method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> confirmButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissButton, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
- }
-
-}
-
package androidx.wear.compose.material3.lazy {
public final class LazyColumnScrollTransformModifiersKt {
diff --git a/wear/compose/compose-material3/build.gradle b/wear/compose/compose-material3/build.gradle
index f103f85..43171ec 100644
--- a/wear/compose/compose-material3/build.gradle
+++ b/wear/compose/compose-material3/build.gradle
@@ -32,21 +32,22 @@
}
dependencies {
- api("androidx.compose.foundation:foundation:1.7.0-rc01")
- api("androidx.compose.ui:ui:1.7.0-rc01")
- api("androidx.compose.ui:ui-text:1.7.0-rc01")
- api("androidx.compose.runtime:runtime:1.7.0-rc01")
+ api("androidx.compose.foundation:foundation:1.7.0")
+ api("androidx.compose.ui:ui:1.7.0")
+ api("androidx.compose.ui:ui-text:1.7.0")
+ api("androidx.compose.runtime:runtime:1.7.0")
api(project(":wear:compose:compose-foundation"))
implementation(libs.kotlinStdlib)
implementation(libs.kotlinCoroutinesCore)
- implementation("androidx.compose.animation:animation:1.7.0-rc01")
- implementation("androidx.compose.material:material-icons-core:1.7.0-rc01")
- implementation("androidx.compose.material:material-ripple:1.7.0-rc01")
- implementation("androidx.compose.ui:ui-util:1.7.0-rc01")
+ implementation("androidx.compose.animation:animation:1.7.0")
+ implementation("androidx.compose.material:material-icons-core:1.7.0")
+ implementation("androidx.compose.material:material-ripple:1.7.0")
+ implementation("androidx.compose.ui:ui-util:1.7.0")
implementation(project(":wear:compose:compose-material-core"))
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
implementation("androidx.graphics:graphics-shapes:1.0.0-beta01")
+ implementation project(':compose:animation:animation-graphics')
androidTestImplementation(project(":compose:ui:ui-test"))
androidTestImplementation(project(":compose:ui:ui-test-junit4"))
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/dialogs/AlertDialogs.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/AlertDialogs.kt
similarity index 95%
rename from wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/dialogs/AlertDialogs.kt
rename to wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/AlertDialogs.kt
index c71f190..4440425 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/dialogs/AlertDialogs.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/AlertDialogs.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.wear.compose.material3.demos.dialogs
+package androidx.wear.compose.material3.demos
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
@@ -38,6 +38,8 @@
import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.material3.AlertDialog
+import androidx.wear.compose.material3.AlertDialogDefaults
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ListHeader
import androidx.wear.compose.material3.MaterialTheme
@@ -45,11 +47,9 @@
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.SwitchButton
import androidx.wear.compose.material3.Text
-import androidx.wear.compose.material3.dialog.AlertDialog
-import androidx.wear.compose.material3.dialog.AlertDialogDefaults
-import androidx.wear.compose.material3.samples.dialog.AlertDialogWithBottomButtonSample
-import androidx.wear.compose.material3.samples.dialog.AlertDialogWithConfirmAndDismissSample
-import androidx.wear.compose.material3.samples.dialog.AlertDialogWithContentGroupsSample
+import androidx.wear.compose.material3.samples.AlertDialogWithBottomButtonSample
+import androidx.wear.compose.material3.samples.AlertDialogWithConfirmAndDismissSample
+import androidx.wear.compose.material3.samples.AlertDialogWithContentGroupsSample
val AlertDialogs =
listOf(
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt
index 9b5316a..1f5758e 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt
@@ -21,7 +21,6 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
@@ -31,7 +30,6 @@
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ButtonColors
import androidx.wear.compose.material3.ButtonDefaults
@@ -74,7 +72,7 @@
modifier = Modifier.fillMaxWidth()
)
},
- modifier = Modifier.width(150.dp)
+ modifier = Modifier.fillMaxWidth()
)
}
item {
@@ -88,11 +86,11 @@
)
},
enabled = false,
- modifier = Modifier.width(150.dp)
+ modifier = Modifier.fillMaxWidth()
)
}
item { ListHeader { Text("3 slot button") } }
- item { ButtonSample() }
+ item { ButtonSample(modifier = Modifier.fillMaxWidth()) }
item {
Button(
onClick = { /* Do something */ },
@@ -105,14 +103,17 @@
modifier = Modifier.size(ButtonDefaults.IconSize)
)
},
- enabled = false
+ enabled = false,
+ modifier = Modifier.fillMaxWidth()
)
}
item { ListHeader { Text("Long Click") } }
item {
- ButtonWithOnLongClickSample({ showOnClickToast(context) }) {
- showOnLongClickToast(context)
- }
+ ButtonWithOnLongClickSample(
+ modifier = Modifier.fillMaxWidth(),
+ onClickHandler = { showOnClickToast(context) },
+ onLongClickHandler = { showOnLongClickToast(context) },
+ )
}
}
}
@@ -124,15 +125,17 @@
item { ListHeader { Text("1 slot button") } }
item { SimpleFilledTonalButtonSample() }
item {
- FilledTonalButtonWithOnLongClickSample({ showOnClickToast(context) }) {
- showOnLongClickToast(context)
- }
+ FilledTonalButtonWithOnLongClickSample(
+ onClickHandler = { showOnClickToast(context) },
+ onLongClickHandler = { showOnLongClickToast(context) }
+ )
}
item {
FilledTonalButton(
onClick = { /* Do something */ },
label = { Text("Filled Tonal Button") },
- enabled = false
+ enabled = false,
+ modifier = Modifier.fillMaxWidth(),
)
}
item { ListHeader { Text("3 slot button") } }
@@ -149,7 +152,8 @@
modifier = Modifier.size(ButtonDefaults.IconSize)
)
},
- enabled = false
+ enabled = false,
+ modifier = Modifier.fillMaxWidth(),
)
}
}
@@ -165,7 +169,8 @@
onClick = { /* Do something */ },
colors = ButtonDefaults.filledVariantButtonColors(),
label = { Text("Filled Variant Button") },
- enabled = false
+ enabled = false,
+ modifier = Modifier.fillMaxWidth()
)
}
item { ListHeader { Text("3 slot button") } }
@@ -183,7 +188,8 @@
modifier = Modifier.size(ButtonDefaults.IconSize)
)
},
- enabled = false
+ enabled = false,
+ modifier = Modifier.fillMaxWidth()
)
}
}
@@ -196,15 +202,17 @@
item { ListHeader { Text("1 slot button") } }
item { SimpleOutlinedButtonSample() }
item {
- OutlinedButtonWithOnLongClickSample({ showOnClickToast(context) }) {
- showOnLongClickToast(context)
- }
+ OutlinedButtonWithOnLongClickSample(
+ onClickHandler = { showOnClickToast(context) },
+ onLongClickHandler = { showOnLongClickToast(context) }
+ )
}
item {
OutlinedButton(
onClick = { /* Do something */ },
label = { Text("Outlined Button") },
- enabled = false
+ enabled = false,
+ modifier = Modifier.fillMaxWidth(),
)
}
item { ListHeader { Text("3 slot button") } }
@@ -221,7 +229,8 @@
modifier = Modifier.size(ButtonDefaults.IconSize)
)
},
- enabled = false
+ enabled = false,
+ modifier = Modifier.fillMaxWidth()
)
}
}
@@ -234,15 +243,17 @@
item { ListHeader { Text("1 slot button") } }
item { SimpleChildButtonSample() }
item {
- ChildButtonWithOnLongClickSample({ showOnClickToast(context) }) {
- showOnLongClickToast(context)
- }
+ ChildButtonWithOnLongClickSample(
+ onClickHandler = { showOnClickToast(context) },
+ onLongClickHandler = { showOnLongClickToast(context) },
+ )
}
item {
ChildButton(
onClick = { /* Do something */ },
label = { Text("Child Button") },
- enabled = false
+ enabled = false,
+ modifier = Modifier.fillMaxWidth(),
)
}
item { ListHeader { Text("3 slot button") } }
@@ -259,7 +270,8 @@
modifier = Modifier.size(ButtonDefaults.IconSize)
)
},
- enabled = false
+ enabled = false,
+ modifier = Modifier.fillMaxWidth()
)
}
}
@@ -274,7 +286,7 @@
CompactButton(
onClick = { /* Do something */ },
colors = ButtonDefaults.buttonColors(),
- modifier = Modifier.width(150.dp)
+ modifier = Modifier.fillMaxWidth()
) {
Text("Compact Button", maxLines = 1, overflow = TextOverflow.Ellipsis)
}
@@ -283,7 +295,7 @@
CompactButton(
onClick = { /* Do something */ },
colors = ButtonDefaults.filledVariantButtonColors(),
- modifier = Modifier.width(150.dp)
+ modifier = Modifier.fillMaxWidth()
) {
Text("Filled Variant", maxLines = 1, overflow = TextOverflow.Ellipsis)
}
@@ -292,7 +304,7 @@
CompactButton(
onClick = { /* Do something */ },
colors = ButtonDefaults.filledTonalButtonColors(),
- modifier = Modifier.width(150.dp)
+ modifier = Modifier.fillMaxWidth()
) {
Text("Filled Tonal", maxLines = 1, overflow = TextOverflow.Ellipsis)
}
@@ -302,7 +314,7 @@
onClick = { /* Do something */ },
colors = ButtonDefaults.outlinedButtonColors(),
border = ButtonDefaults.outlinedButtonBorder(enabled = true),
- modifier = Modifier.width(150.dp)
+ modifier = Modifier.fillMaxWidth()
) {
Text("Outlined", maxLines = 1, overflow = TextOverflow.Ellipsis)
}
@@ -314,7 +326,7 @@
onClick = { /* Do something */ },
icon = { StandardIcon(ButtonDefaults.SmallIconSize) },
colors = ButtonDefaults.filledVariantButtonColors(),
- modifier = Modifier.width(150.dp)
+ modifier = Modifier.fillMaxWidth()
) {
Text("Filled Variant", maxLines = 1, overflow = TextOverflow.Ellipsis)
}
@@ -324,7 +336,7 @@
onClick = { /* Do something */ },
icon = { StandardIcon(ButtonDefaults.SmallIconSize) },
colors = ButtonDefaults.filledTonalButtonColors(),
- modifier = Modifier.width(150.dp)
+ modifier = Modifier.fillMaxWidth()
) {
Text("Filled Tonal", maxLines = 1, overflow = TextOverflow.Ellipsis)
}
@@ -335,7 +347,7 @@
icon = { StandardIcon(ButtonDefaults.SmallIconSize) },
colors = ButtonDefaults.outlinedButtonColors(),
border = ButtonDefaults.outlinedButtonBorder(enabled = true),
- modifier = Modifier.width(150.dp)
+ modifier = Modifier.fillMaxWidth()
) {
Text("Outlined", maxLines = 1, overflow = TextOverflow.Ellipsis)
}
@@ -345,7 +357,7 @@
onClick = { /* Do something */ },
icon = { StandardIcon(ButtonDefaults.SmallIconSize) },
colors = ButtonDefaults.childButtonColors(),
- modifier = Modifier.width(150.dp)
+ modifier = Modifier.fillMaxWidth()
) {
Text("Child", maxLines = 1, overflow = TextOverflow.Ellipsis)
}
@@ -354,14 +366,14 @@
item {
CompactButton(
onClick = { /* Do something */ },
- icon = { StandardIcon(ButtonDefaults.SmallIconSize) }
+ icon = { StandardIcon(ButtonDefaults.SmallIconSize) },
)
}
item {
CompactButton(
onClick = { /* Do something */ },
icon = { StandardIcon(ButtonDefaults.SmallIconSize) },
- colors = ButtonDefaults.filledTonalButtonColors()
+ colors = ButtonDefaults.filledTonalButtonColors(),
)
}
item {
@@ -369,21 +381,22 @@
onClick = { /* Do something */ },
icon = { StandardIcon(ButtonDefaults.SmallIconSize) },
colors = ButtonDefaults.outlinedButtonColors(),
- border = ButtonDefaults.outlinedButtonBorder(enabled = true)
+ border = ButtonDefaults.outlinedButtonBorder(enabled = true),
)
}
item {
CompactButton(
onClick = { /* Do something */ },
icon = { StandardIcon(ButtonDefaults.SmallIconSize) },
- colors = ButtonDefaults.childButtonColors()
+ colors = ButtonDefaults.childButtonColors(),
)
}
item { ListHeader { Text("Long Click") } }
item {
- CompactButtonWithOnLongClickSample({ showOnClickToast(context) }) {
- showOnLongClickToast(context)
- }
+ CompactButtonWithOnLongClickSample(
+ onClickHandler = { showOnClickToast(context) },
+ onLongClickHandler = { showOnLongClickToast(context) }
+ )
}
item { ListHeader { Text("Expandable") } }
item { OutlinedCompactButtonSample() }
@@ -469,6 +482,7 @@
label = label,
enabled = enabled,
colors = colors,
+ modifier = Modifier.fillMaxWidth(),
)
}
@@ -501,6 +515,7 @@
secondaryLabel = secondaryLabel,
enabled = enabled,
colors = colors,
+ modifier = Modifier.fillMaxWidth()
)
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ColorSchemeDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ColorSchemeDemo.kt
index cf42421..b99eb36 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ColorSchemeDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ColorSchemeDemo.kt
@@ -17,12 +17,10 @@
package androidx.wear.compose.material3.demos
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.ListHeader
@@ -249,7 +247,7 @@
Button(
onClick = {},
label = { Text(text, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth()) },
- modifier = Modifier.width(150.dp),
+ modifier = Modifier.fillMaxWidth(),
colors =
ButtonDefaults.buttonColors(
containerColor = containerColor,
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Confirmations.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Confirmations.kt
new file mode 100644
index 0000000..9233b25
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Confirmations.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2024 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.wear.compose.material3.demos
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.material3.Confirmation
+import androidx.wear.compose.material3.ConfirmationDefaults
+import androidx.wear.compose.material3.FilledTonalButton
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.samples.ConfirmationSample
+import androidx.wear.compose.material3.samples.FailureConfirmationSample
+import androidx.wear.compose.material3.samples.LongTextConfirmationSample
+import androidx.wear.compose.material3.samples.SuccessConfirmationSample
+
+val Comfirmations =
+ listOf(
+ ComposableDemo("Generic confirmation") { ConfirmationSample() },
+ ComposableDemo("Long content confirmation") { LongTextConfirmationSample() },
+ ComposableDemo("Success confirmation") { SuccessConfirmationSample() },
+ ComposableDemo("Failure confirmation") { FailureConfirmationSample() },
+ ComposableDemo("Confirmation without text") { ConfirmationWithoutText() },
+ ComposableDemo("Confirmation with custom colors") { ConfirmationWithCustomColors() },
+ )
+
+@Composable
+fun ConfirmationWithoutText() {
+ var showConfirmation by remember { mutableStateOf(false) }
+
+ Box(Modifier.fillMaxSize()) {
+ FilledTonalButton(
+ modifier = Modifier.align(Alignment.Center),
+ onClick = { showConfirmation = true },
+ label = { Text("Show Confirmation") }
+ )
+ }
+
+ Confirmation(
+ show = showConfirmation,
+ onDismissRequest = { showConfirmation = false },
+ curvedText = null
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Add,
+ contentDescription = null,
+ modifier = Modifier.size(ConfirmationDefaults.IconSize).align(Alignment.Center),
+ tint = MaterialTheme.colorScheme.primary
+ )
+ }
+}
+
+@Composable
+fun ConfirmationWithCustomColors() {
+ var showConfirmation by remember { mutableStateOf(false) }
+
+ Box(Modifier.fillMaxSize()) {
+ FilledTonalButton(
+ modifier = Modifier.align(Alignment.Center),
+ onClick = { showConfirmation = true },
+ label = { Text("Show Confirmation") }
+ )
+ }
+
+ Confirmation(
+ show = showConfirmation,
+ onDismissRequest = { showConfirmation = false },
+ colors =
+ ConfirmationDefaults.confirmationColors(
+ iconColor = MaterialTheme.colorScheme.tertiary,
+ iconContainerColor = MaterialTheme.colorScheme.onTertiary,
+ textColor = MaterialTheme.colorScheme.onSurfaceVariant
+ ),
+ curvedText = ConfirmationDefaults.curvedText("Custom confirmation")
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Add,
+ contentDescription = null,
+ modifier = Modifier.size(ConfirmationDefaults.IconSize).align(Alignment.Center),
+ )
+ }
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/CurvedTextDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/CurvedTextDemo.kt
index 63b94d9..9be1de6 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/CurvedTextDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/CurvedTextDemo.kt
@@ -16,8 +16,12 @@
package androidx.wear.compose.material3.demos
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -26,7 +30,9 @@
import androidx.wear.compose.foundation.CurvedLayout
import androidx.wear.compose.foundation.CurvedModifier
import androidx.wear.compose.foundation.angularSizeDp
+import androidx.wear.compose.foundation.background
import androidx.wear.compose.foundation.curvedBox
+import androidx.wear.compose.foundation.curvedRow
import androidx.wear.compose.integration.demos.common.ComposableDemo
import androidx.wear.compose.material3.CurvedTextDefaults
import androidx.wear.compose.material3.curvedText
@@ -45,37 +51,53 @@
@Composable
fun LargerFont() {
- CurvedLayout(radialAlignment = CurvedAlignment.Radial.Center) {
- curvedText("Larger", fontSize = 24.sp)
- curvedBox(CurvedModifier.angularSizeDp(5.dp)) {}
- curvedText("Normal")
+ val backgroundColor = CurvedTextDefaults.backgroundColor()
+ CurvedLayout(modifier = Modifier.background(Color.DarkGray)) {
+ curvedRow(
+ CurvedModifier.background(backgroundColor, StrokeCap.Round),
+ radialAlignment = CurvedAlignment.Radial.Center
+ ) {
+ curvedText("Larger", fontSize = 24.sp)
+ curvedBox(CurvedModifier.angularSizeDp(5.dp)) {}
+ curvedText("Normal")
+ }
}
}
@Composable
fun KerningDemo() {
- Box {
- CurvedLayout { curvedText("MMMMMMMM") }
+ val backgroundColor = CurvedTextDefaults.backgroundColor()
+ Box(Modifier.background(Color.DarkGray)) {
+ CurvedLayout {
+ curvedText("MMMMMMMM", CurvedModifier.background(backgroundColor, StrokeCap.Round))
+ }
CurvedLayout(anchor = 90f, angularDirection = CurvedDirection.Angular.Reversed) {
- curvedText("MMMMMMMM")
+ curvedText("MMMMMMMM", CurvedModifier.background(backgroundColor, StrokeCap.Round))
}
}
}
@Composable
fun SmallArcDemo() {
- CurvedLayout {
+ val backgroundColor = CurvedTextDefaults.backgroundColor()
+ CurvedLayout(Modifier.background(Color.DarkGray)) {
// Default sweep is 70 degrees
- curvedText("Long text that will be cut for sure.", overflow = TextOverflow.Ellipsis)
+ curvedText(
+ "Long text that will be cut for sure.",
+ CurvedModifier.background(backgroundColor, StrokeCap.Round),
+ overflow = TextOverflow.Ellipsis
+ )
}
}
@Composable
fun LargeArcDemo() {
- CurvedLayout {
+ val backgroundColor = CurvedTextDefaults.backgroundColor()
+ CurvedLayout(Modifier.background(Color.DarkGray)) {
// Static content can use 120 degrees
curvedText(
"Long text that will be cut for sure.",
+ CurvedModifier.background(backgroundColor, StrokeCap.Round),
maxSweepAngle = CurvedTextDefaults.StaticContentMaxSweepAngle,
overflow = TextOverflow.Ellipsis
)
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/DatePickerDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/DatePickerDemo.kt
new file mode 100644
index 0000000..fda5b03
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/DatePickerDemo.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 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.wear.compose.material3.demos
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.material3.samples.DatePickerFromDateToDateSample
+import androidx.wear.compose.material3.samples.DatePickerSample
+import androidx.wear.compose.material3.samples.DatePickerYearMonthDaySample
+
+@RequiresApi(Build.VERSION_CODES.O)
+val DatePickerDemos =
+ listOf(
+ ComposableDemo("Date Year-Month-Day") { DatePickerYearMonthDaySample() },
+ ComposableDemo("Date System date format") { DatePickerSample() },
+ ComposableDemo("Date Range") { DatePickerFromDateToDateSample() },
+ )
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt
index cd8d533..8a9bbea 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt
@@ -18,14 +18,13 @@
import android.view.HapticFeedbackConstants
import android.view.View
-import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.ListHeader
import androidx.wear.compose.material3.Text
@Composable
@@ -64,7 +63,8 @@
Pair(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE, "Virtual Key Release"),
)
- ScalingLazyDemo(contentPadding = PaddingValues(horizontal = 20.dp)) {
+ ScalingLazyDemo {
+ item { ListHeader { Text("Haptic Constants") } }
items(hapticConstants.size) { index ->
val (constant, name) = hapticConstants[index]
HapticsDemo(haptics, constant, name)
@@ -89,6 +89,6 @@
private class HapticFeedbackProvider(private val view: View) {
fun performHapticFeedback(feedbackConstant: Int) {
- view.let { view -> view.performHapticFeedback(feedbackConstant) }
+ view.performHapticFeedback(feedbackConstant)
}
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconButtonDemo.kt
index 7204483..0d75c07 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconButtonDemo.kt
@@ -16,7 +16,6 @@
package androidx.wear.compose.material3.demos
-import androidx.compose.foundation.Image
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -27,10 +26,6 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.painter.Painter
-import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
@@ -47,6 +42,8 @@
import androidx.wear.compose.material3.samples.FilledTonalIconButtonSample
import androidx.wear.compose.material3.samples.FilledVariantIconButtonSample
import androidx.wear.compose.material3.samples.IconButtonSample
+import androidx.wear.compose.material3.samples.IconButtonWithCornerAnimationSample
+import androidx.wear.compose.material3.samples.IconButtonWithImageSample
import androidx.wear.compose.material3.samples.IconButtonWithOnLongClickSample
import androidx.wear.compose.material3.samples.OutlinedIconButtonSample
import androidx.wear.compose.material3.touchTargetAwareSize
@@ -112,24 +109,9 @@
item { ListHeader { Text("Corner Animation") } }
item {
Row {
- val interactionSource1 = remember { MutableInteractionSource() }
- FilledIconButton(
- onClick = {},
- shape = IconButtonDefaults.animatedShape(interactionSource1),
- interactionSource = interactionSource1
- ) {
- StandardIcon(ButtonDefaults.IconSize)
- }
+ IconButtonWithCornerAnimationSample()
Spacer(modifier = Modifier.width(5.dp))
- val interactionSource2 = remember { MutableInteractionSource() }
- FilledIconButton(
- onClick = {},
- colors = IconButtonDefaults.filledVariantIconButtonColors(),
- shape = IconButtonDefaults.animatedShape(interactionSource2),
- interactionSource = interactionSource2
- ) {
- StandardIcon(ButtonDefaults.IconSize)
- }
+ IconButtonWithCornerAnimationSample()
}
}
item { ListHeader { Text("Morphed Animation") } }
@@ -165,14 +147,6 @@
}
}
}
- item { ListHeader { Text("Image Button") } }
- item {
- Row(verticalAlignment = Alignment.CenterVertically) {
- ImageRoundButton(painterResource(R.drawable.card_background), {})
- Spacer(modifier = Modifier.width(5.dp))
- ImageRoundButton(painterResource(R.drawable.card_background), {}, enabled = false)
- }
- }
item { ListHeader { Text("Sizes") } }
item {
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -206,6 +180,43 @@
}
@Composable
+fun ImageButtonDemo() {
+ ScalingLazyDemo {
+ item { ListHeader { Text("Image Button") } }
+ item {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ IconButtonWithImageSample(
+ painterResource(R.drawable.card_background),
+ enabled = true,
+ )
+ Spacer(modifier = Modifier.width(5.dp))
+ IconButtonWithImageSample(
+ painterResource(R.drawable.card_background),
+ enabled = false
+ )
+ }
+ }
+ item { ListHeader { Text("Animated Shape") } }
+ item {
+ val interactionSource = remember { MutableInteractionSource() }
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ IconButtonWithImageSample(
+ painterResource(R.drawable.card_background),
+ enabled = true,
+ interactionSource = interactionSource,
+ shape = IconButtonDefaults.animatedShape(interactionSource)
+ )
+ Spacer(modifier = Modifier.width(5.dp))
+ IconButtonWithImageSample(
+ painterResource(R.drawable.card_background),
+ enabled = false,
+ )
+ }
+ }
+ }
+}
+
+@Composable
private fun IconButtonWithSize(size: Dp) {
FilledTonalIconButton(
modifier = Modifier.touchTargetAwareSize(size),
@@ -214,32 +225,3 @@
StandardIcon(IconButtonDefaults.iconSizeFor(size))
}
}
-
-@Composable
-fun ImageRoundButton(
- painter: Painter,
- onClick: () -> Unit,
- modifier: Modifier = Modifier,
- onLongClick: (() -> Unit)? = null,
- onLongClickLabel: String? = null,
- enabled: Boolean = true,
- shape: Shape = IconButtonDefaults.shape,
- interactionSource: MutableInteractionSource? = null,
-) {
- IconButton(
- onClick = onClick,
- modifier = modifier,
- onLongClick = onLongClick,
- onLongClickLabel = onLongClickLabel,
- enabled = enabled,
- shape = shape,
- interactionSource = interactionSource
- ) {
- Image(
- painter = painter,
- contentDescription = null,
- contentScale = ContentScale.Crop,
- modifier = if (enabled) Modifier else Modifier.alpha(0.38f)
- )
- }
-}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt
index 3d39cb3..394a52f 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt
@@ -21,6 +21,8 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
@@ -28,7 +30,8 @@
@Composable
fun ScalingLazyDemo(
- contentPadding: PaddingValues = PaddingValues(),
+ contentPadding: PaddingValues =
+ PaddingValues(horizontal = LocalConfiguration.current.screenWidthDp.dp * 0.052f),
content: ScalingLazyListScope.() -> Unit
) {
val scrollState = rememberScalingLazyListState()
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/OpenOnPhoneDialogDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/OpenOnPhoneDialogDemo.kt
new file mode 100644
index 0000000..ab628e5
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/OpenOnPhoneDialogDemo.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 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.wear.compose.material3.demos
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.material3.FilledTonalButton
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.OpenOnPhoneDialog
+import androidx.wear.compose.material3.OpenOnPhoneDialogDefaults
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.samples.OpenOnPhoneDialogSample
+
+val OpenOnPhoneDialogDemos =
+ listOf(
+ ComposableDemo("Default OpenOnPhone Dialog") { OpenOnPhoneDialogSample() },
+ ComposableDemo("With custom text") { OpenOnPhoneDialogWithCustomText() },
+ ComposableDemo("With custom colors") { OpenOnPhoneDialogWithCustomColors() },
+ )
+
+@Composable
+fun OpenOnPhoneDialogWithCustomText() {
+ var showConfirmation by remember { mutableStateOf(false) }
+
+ Box(Modifier.fillMaxSize()) {
+ FilledTonalButton(
+ modifier = Modifier.align(Alignment.Center),
+ onClick = { showConfirmation = true },
+ label = { Text("Open on phone") }
+ )
+ }
+
+ OpenOnPhoneDialog(
+ show = showConfirmation,
+ onDismissRequest = { showConfirmation = false },
+ curvedText = OpenOnPhoneDialogDefaults.curvedText("Custom text")
+ )
+}
+
+@Composable
+fun OpenOnPhoneDialogWithCustomColors() {
+ var showConfirmation by remember { mutableStateOf(false) }
+
+ Box(Modifier.fillMaxSize()) {
+ FilledTonalButton(
+ modifier = Modifier.align(Alignment.Center),
+ onClick = { showConfirmation = true },
+ label = { Text("Open on phone") }
+ )
+ }
+
+ OpenOnPhoneDialog(
+ show = showConfirmation,
+ onDismissRequest = { showConfirmation = false },
+ colors =
+ OpenOnPhoneDialogDefaults.colors(
+ iconColor = MaterialTheme.colorScheme.tertiary,
+ iconContainerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ progressIndicatorColor = MaterialTheme.colorScheme.tertiary,
+ progressTrackColor = MaterialTheme.colorScheme.onTertiary,
+ textColor = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ )
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt
index 7149870..83f1564 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt
@@ -16,88 +16,27 @@
package androidx.wear.compose.material3.demos
-import android.os.Build
-import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Edit
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
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.Modifier
import androidx.compose.ui.unit.dp
import androidx.wear.compose.integration.demos.common.ComposableDemo
-import androidx.wear.compose.material3.Button
-import androidx.wear.compose.material3.Icon
import androidx.wear.compose.material3.Picker
import androidx.wear.compose.material3.Text
-import androidx.wear.compose.material3.TimePicker
-import androidx.wear.compose.material3.TimePickerType
import androidx.wear.compose.material3.rememberPickerState
import androidx.wear.compose.material3.samples.AutoCenteringPickerGroup
import androidx.wear.compose.material3.samples.PickerAnimateScrollToOption
import androidx.wear.compose.material3.samples.PickerGroupSample
import androidx.wear.compose.material3.samples.SimplePicker
-import androidx.wear.compose.material3.samples.TimePickerSample
-import androidx.wear.compose.material3.samples.TimePickerWith12HourClockSample
-import androidx.wear.compose.material3.samples.TimePickerWithSecondsSample
-import java.time.LocalTime
-import java.time.format.DateTimeFormatter
val PickerDemos =
listOf(
- // Requires API level 26 or higher due to java.time dependency.
- *(if (Build.VERSION.SDK_INT >= 26)
- arrayOf(
- ComposableDemo("Time HH:MM:SS") { TimePickerWithSecondsSample() },
- ComposableDemo("Time HH:MM") {
- var showTimePicker by remember { mutableStateOf(true) }
- var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
- val formatter = DateTimeFormatter.ofPattern("HH:mm")
- if (showTimePicker) {
- TimePicker(
- onTimePicked = {
- timePickerTime = it
- showTimePicker = false
- },
- timePickerType = TimePickerType.HoursMinutes24H,
- // Initialize with last picked time on reopen
- initialTime = timePickerTime
- )
- } else {
- Column(
- modifier = Modifier.fillMaxSize(),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Text("Selected Time")
- Spacer(Modifier.height(12.dp))
- Button(
- onClick = { showTimePicker = true },
- label = { Text(timePickerTime.format(formatter)) },
- icon = {
- Icon(
- imageVector = Icons.Filled.Edit,
- contentDescription = "Edit"
- )
- },
- )
- }
- }
- },
- ComposableDemo("Time 12 Hour") { TimePickerWith12HourClockSample() },
- ComposableDemo("Time System time format") { TimePickerSample() },
- )
- else emptyArray<ComposableDemo>()),
ComposableDemo("Simple Picker") { SimplePicker() },
ComposableDemo("No gradient") { PickerWithoutGradient() },
ComposableDemo("Animate picker change") { PickerAnimateScrollToOption() },
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt
new file mode 100644
index 0000000..92d8d87
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2024 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.wear.compose.material3.demos
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.TimePicker
+import androidx.wear.compose.material3.TimePickerType
+import androidx.wear.compose.material3.samples.TimePickerSample
+import androidx.wear.compose.material3.samples.TimePickerWith12HourClockSample
+import androidx.wear.compose.material3.samples.TimePickerWithSecondsSample
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+
+@RequiresApi(Build.VERSION_CODES.O)
+val TimePickerDemos =
+ listOf(
+ ComposableDemo("Time HH:MM:SS") { TimePickerWithSecondsSample() },
+ ComposableDemo("Time HH:MM") { TimePicker24hWithoutSecondsDemo() },
+ ComposableDemo("Time 12 Hour") { TimePickerWith12HourClockSample() },
+ ComposableDemo("Time System time format") { TimePickerSample() },
+ )
+
+@RequiresApi(Build.VERSION_CODES.O)
+@Composable
+private fun TimePicker24hWithoutSecondsDemo() {
+ var showTimePicker by remember { mutableStateOf(true) }
+ var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
+ val formatter = DateTimeFormatter.ofPattern("HH:mm")
+ if (showTimePicker) {
+ TimePicker(
+ onTimePicked = {
+ timePickerTime = it
+ showTimePicker = false
+ },
+ timePickerType = TimePickerType.HoursMinutes24H,
+ // Initialize with last picked time on reopen
+ initialTime = timePickerTime
+ )
+ } else {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text("Selected Time")
+ Spacer(Modifier.height(12.dp))
+ Button(
+ onClick = { showTimePicker = true },
+ label = { Text(timePickerTime.format(formatter)) },
+ icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
+ )
+ }
+ }
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index 5c496f4..2f9ce14 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -22,7 +22,6 @@
import androidx.wear.compose.integration.demos.common.Centralize
import androidx.wear.compose.integration.demos.common.ComposableDemo
import androidx.wear.compose.integration.demos.common.Material3DemoCategory
-import androidx.wear.compose.material3.demos.dialogs.AlertDialogs
import androidx.wear.compose.material3.samples.AnimatedTextSample
import androidx.wear.compose.material3.samples.AnimatedTextSampleButtonResponse
import androidx.wear.compose.material3.samples.AnimatedTextSampleSharedFontRegistry
@@ -46,12 +45,9 @@
listOf(
ComposableDemo("Color Scheme") { ColorSchemeDemos() },
Material3DemoCategory("Curved Text", CurvedTextDemos),
- Material3DemoCategory(
- "Dialogs",
- listOf(
- Material3DemoCategory("AlertDialog", AlertDialogs),
- )
- ),
+ Material3DemoCategory("Alert Dialog", AlertDialogs),
+ Material3DemoCategory("Confirmation", Comfirmations),
+ Material3DemoCategory("Open on phone Dialog", OpenOnPhoneDialogDemos),
ComposableDemo("Scaffold") { ScaffoldSample() },
Material3DemoCategory("ScrollAway", ScrollAwayDemos),
ComposableDemo("Haptics") { Centralize { HapticsDemos() } },
@@ -70,6 +66,7 @@
),
ComposableDemo("Compact Button") { CompactButtonDemo() },
ComposableDemo("Icon Button") { IconButtonDemo() },
+ ComposableDemo("Image Button") { ImageButtonDemo() },
ComposableDemo("Text Button") { TextButtonDemo() },
Material3DemoCategory(
"Edge Button",
@@ -123,6 +120,13 @@
),
Material3DemoCategory("Slider", SliderDemos),
Material3DemoCategory("Picker", PickerDemos),
+ // Requires API level 26 or higher due to java.time dependency.
+ *(if (Build.VERSION.SDK_INT >= 26)
+ arrayOf(
+ Material3DemoCategory("TimePicker", TimePickerDemos),
+ Material3DemoCategory("DatePicker", DatePickerDemos)
+ )
+ else emptyArray<Material3DemoCategory>()),
Material3DemoCategory("Progress Indicator", ProgressIndicatorDemos),
Material3DemoCategory("Scroll Indicator", ScrollIndicatorDemos),
Material3DemoCategory("Placeholder", PlaceholderDemos),
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/dialog/AlertDialogSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/AlertDialogSample.kt
similarity index 96%
rename from wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/dialog/AlertDialogSample.kt
rename to wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/AlertDialogSample.kt
index e20161b..dae4eef 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/dialog/AlertDialogSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/AlertDialogSample.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.wear.compose.material3.samples.dialog
+package androidx.wear.compose.material3.samples
import androidx.annotation.Sampled
import androidx.compose.foundation.layout.Box
@@ -31,12 +31,12 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.AlertDialog
+import androidx.wear.compose.material3.AlertDialogDefaults
import androidx.wear.compose.material3.FilledTonalButton
import androidx.wear.compose.material3.Icon
import androidx.wear.compose.material3.MaterialTheme
import androidx.wear.compose.material3.Text
-import androidx.wear.compose.material3.dialog.AlertDialog
-import androidx.wear.compose.material3.dialog.AlertDialogDefaults
@Sampled
@Composable
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonSample.kt
index 15b722e..b751fc8 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonSample.kt
@@ -17,6 +17,7 @@
package androidx.wear.compose.material3.samples
import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
@@ -41,19 +42,24 @@
@Sampled
@Composable
-fun ButtonWithOnLongClickSample(onClickHandler: () -> Unit, onLongClickHandler: () -> Unit) {
+fun ButtonWithOnLongClickSample(
+ onClickHandler: () -> Unit,
+ onLongClickHandler: () -> Unit,
+ modifier: Modifier = Modifier.fillMaxWidth(),
+) {
Button(
onClick = onClickHandler,
onLongClick = onLongClickHandler,
onLongClickLabel = "Long click",
label = { Text("Button") },
- secondaryLabel = { Text("with long click") }
+ secondaryLabel = { Text("with long click") },
+ modifier = modifier,
)
}
@Sampled
@Composable
-fun ButtonSample() {
+fun ButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
Button(
onClick = { /* Do something */ },
label = { Text("Button") },
@@ -64,34 +70,41 @@
contentDescription = "Favorite icon",
modifier = Modifier.size(ButtonDefaults.IconSize)
)
- }
+ },
+ modifier = modifier
)
}
@Sampled
@Composable
-fun SimpleFilledTonalButtonSample() {
- FilledTonalButton(onClick = { /* Do something */ }, label = { Text("Filled Tonal Button") })
+fun SimpleFilledTonalButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
+ FilledTonalButton(
+ onClick = { /* Do something */ },
+ label = { Text("Filled Tonal Button") },
+ modifier = modifier,
+ )
}
@Sampled
@Composable
fun FilledTonalButtonWithOnLongClickSample(
onClickHandler: () -> Unit,
- onLongClickHandler: () -> Unit
+ onLongClickHandler: () -> Unit,
+ modifier: Modifier = Modifier.fillMaxWidth()
) {
FilledTonalButton(
onClick = onClickHandler,
onLongClick = onLongClickHandler,
onLongClickLabel = "Long click",
label = { Text("Filled Tonal Button") },
- secondaryLabel = { Text("with long click") }
+ secondaryLabel = { Text("with long click") },
+ modifier = modifier,
)
}
@Sampled
@Composable
-fun FilledTonalButtonSample() {
+fun FilledTonalButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
FilledTonalButton(
onClick = { /* Do something */ },
label = { Text("Filled Tonal Button") },
@@ -102,23 +115,25 @@
contentDescription = "Favorite icon",
modifier = Modifier.size(ButtonDefaults.IconSize)
)
- }
+ },
+ modifier = modifier,
)
}
@Sampled
@Composable
-fun SimpleFilledVariantButtonSample() {
+fun SimpleFilledVariantButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
Button(
onClick = { /* Do something */ },
colors = ButtonDefaults.filledVariantButtonColors(),
- label = { Text("Filled Variant Button") }
+ label = { Text("Filled Variant Button") },
+ modifier = modifier,
)
}
@Sampled
@Composable
-fun FilledVariantButtonSample() {
+fun FilledVariantButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
Button(
onClick = { /* Do something */ },
colors = ButtonDefaults.filledVariantButtonColors(),
@@ -130,34 +145,41 @@
contentDescription = "Favorite icon",
modifier = Modifier.size(ButtonDefaults.IconSize)
)
- }
+ },
+ modifier = modifier
)
}
@Sampled
@Composable
-fun SimpleOutlinedButtonSample() {
- OutlinedButton(onClick = { /* Do something */ }, label = { Text("Outlined Button") })
+fun SimpleOutlinedButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
+ OutlinedButton(
+ onClick = { /* Do something */ },
+ label = { Text("Outlined Button") },
+ modifier = modifier,
+ )
}
@Sampled
@Composable
fun OutlinedButtonWithOnLongClickSample(
onClickHandler: () -> Unit,
- onLongClickHandler: () -> Unit
+ onLongClickHandler: () -> Unit,
+ modifier: Modifier = Modifier.fillMaxWidth()
) {
OutlinedButton(
onClick = onClickHandler,
onLongClick = onLongClickHandler,
onLongClickLabel = "Long click",
label = { Text("Outlined Button") },
- secondaryLabel = { Text("with long click") }
+ secondaryLabel = { Text("with long click") },
+ modifier = modifier,
)
}
@Sampled
@Composable
-fun OutlinedButtonSample() {
+fun OutlinedButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
OutlinedButton(
onClick = { /* Do something */ },
label = { Text("Outlined Button") },
@@ -168,31 +190,41 @@
contentDescription = "Favorite icon",
modifier = Modifier.size(ButtonDefaults.IconSize)
)
- }
+ },
+ modifier = modifier,
)
}
@Sampled
@Composable
-fun SimpleChildButtonSample() {
- ChildButton(onClick = { /* Do something */ }, label = { Text("Child Button") })
+fun SimpleChildButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
+ ChildButton(
+ onClick = { /* Do something */ },
+ label = { Text("Child Button") },
+ modifier = modifier,
+ )
}
@Sampled
@Composable
-fun ChildButtonWithOnLongClickSample(onClickHandler: () -> Unit, onLongClickHandler: () -> Unit) {
+fun ChildButtonWithOnLongClickSample(
+ onClickHandler: () -> Unit,
+ onLongClickHandler: () -> Unit,
+ modifier: Modifier = Modifier.fillMaxWidth()
+) {
ChildButton(
onClick = onClickHandler,
onLongClick = onLongClickHandler,
onLongClickLabel = "Long click",
label = { Text("Child Button") },
- secondaryLabel = { Text("with long click") }
+ secondaryLabel = { Text("with long click") },
+ modifier = modifier,
)
}
@Sampled
@Composable
-fun ChildButtonSample() {
+fun ChildButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
ChildButton(
onClick = { /* Do something */ },
label = { Text("Child Button") },
@@ -203,41 +235,14 @@
contentDescription = "Favorite icon",
modifier = Modifier.size(ButtonDefaults.IconSize)
)
- }
+ },
+ modifier = modifier
)
}
@Sampled
@Composable
-fun CompactButtonSample() {
- CompactButton(
- onClick = { /* Do something */ },
- icon = {
- Icon(
- Icons.Filled.Favorite,
- contentDescription = "Favorite icon",
- modifier = Modifier.size(ButtonDefaults.SmallIconSize)
- )
- }
- ) {
- Text("Compact Button", maxLines = 1, overflow = TextOverflow.Ellipsis)
- }
-}
-
-@Sampled
-@Composable
-fun CompactButtonWithOnLongClickSample(onClickHandler: () -> Unit, onLongClickHandler: () -> Unit) {
- CompactButton(
- onClick = onClickHandler,
- onLongClick = onLongClickHandler,
- onLongClickLabel = "Long click",
- label = { Text("Long clickable") }
- )
-}
-
-@Sampled
-@Composable
-fun FilledTonalCompactButtonSample() {
+fun CompactButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
CompactButton(
onClick = { /* Do something */ },
icon = {
@@ -247,7 +252,42 @@
modifier = Modifier.size(ButtonDefaults.SmallIconSize)
)
},
- colors = ButtonDefaults.filledTonalButtonColors()
+ modifier = modifier,
+ ) {
+ Text("Compact Button", maxLines = 1, overflow = TextOverflow.Ellipsis)
+ }
+}
+
+@Sampled
+@Composable
+fun CompactButtonWithOnLongClickSample(
+ onClickHandler: () -> Unit,
+ onLongClickHandler: () -> Unit,
+ modifier: Modifier = Modifier.fillMaxWidth()
+) {
+ CompactButton(
+ onClick = onClickHandler,
+ onLongClick = onLongClickHandler,
+ onLongClickLabel = "Long click",
+ label = { Text("Long clickable") },
+ modifier = modifier,
+ )
+}
+
+@Sampled
+@Composable
+fun FilledTonalCompactButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
+ CompactButton(
+ onClick = { /* Do something */ },
+ icon = {
+ Icon(
+ Icons.Filled.Favorite,
+ contentDescription = "Favorite icon",
+ modifier = Modifier.size(ButtonDefaults.SmallIconSize)
+ )
+ },
+ colors = ButtonDefaults.filledTonalButtonColors(),
+ modifier = modifier,
) {
Text("Filled Tonal Compact Button", maxLines = 1, overflow = TextOverflow.Ellipsis)
}
@@ -255,7 +295,7 @@
@Sampled
@Composable
-fun OutlinedCompactButtonSample() {
+fun OutlinedCompactButtonSample(modifier: Modifier = Modifier.fillMaxWidth()) {
CompactButton(
onClick = { /* Do something */ },
icon = {
@@ -266,7 +306,8 @@
)
},
colors = ButtonDefaults.outlinedButtonColors(),
- border = ButtonDefaults.outlinedButtonBorder(enabled = true)
+ border = ButtonDefaults.outlinedButtonBorder(enabled = true),
+ modifier = modifier,
) {
Text("Show More", maxLines = 1, overflow = TextOverflow.Ellipsis)
}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ConfirmationSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ConfirmationSample.kt
new file mode 100644
index 0000000..46bde32
--- /dev/null
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ConfirmationSample.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2024 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.wear.compose.material3.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.Confirmation
+import androidx.wear.compose.material3.ConfirmationDefaults
+import androidx.wear.compose.material3.FailureConfirmation
+import androidx.wear.compose.material3.FilledTonalButton
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.SuccessConfirmation
+import androidx.wear.compose.material3.Text
+
+@Sampled
+@Composable
+fun ConfirmationSample() {
+ var showConfirmation by remember { mutableStateOf(false) }
+
+ Box(Modifier.fillMaxSize()) {
+ FilledTonalButton(
+ modifier = Modifier.align(Alignment.Center),
+ onClick = { showConfirmation = true },
+ label = { Text("Show Confirmation") }
+ )
+ }
+
+ // Has an icon and a short curved text content, which will be displayed along the bottom edge of
+ // the screen.
+ Confirmation(
+ show = showConfirmation,
+ onDismissRequest = { showConfirmation = false },
+ curvedText = ConfirmationDefaults.curvedText("Confirmed")
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Add,
+ contentDescription = null,
+ modifier = Modifier.size(ConfirmationDefaults.IconSize),
+ )
+ }
+}
+
+@Sampled
+@Composable
+fun LongTextConfirmationSample() {
+ var showConfirmation by remember { mutableStateOf(false) }
+
+ Box(Modifier.fillMaxSize()) {
+ FilledTonalButton(
+ modifier = Modifier.align(Alignment.Center),
+ onClick = { showConfirmation = true },
+ label = { Text("Show Confirmation") }
+ )
+ }
+
+ // Has an icon and a text content. Text will be displayed in the center of the screen below the
+ // icon.
+ Confirmation(
+ show = showConfirmation,
+ onDismissRequest = { showConfirmation = false },
+ text = { Text(text = "Your message has been sent") },
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Add,
+ contentDescription = null,
+ modifier = Modifier.size(ConfirmationDefaults.SmallIconSize),
+ )
+ }
+}
+
+@Sampled
+@Composable
+fun FailureConfirmationSample() {
+ var showConfirmation by remember { mutableStateOf(false) }
+
+ Box(Modifier.fillMaxSize()) {
+ FilledTonalButton(
+ modifier = Modifier.align(Alignment.Center),
+ onClick = { showConfirmation = true },
+ label = { Text("Show Confirmation") }
+ )
+ }
+
+ FailureConfirmation(show = showConfirmation, onDismissRequest = { showConfirmation = false })
+}
+
+@Sampled
+@Composable
+fun SuccessConfirmationSample() {
+ var showConfirmation by remember { mutableStateOf(false) }
+
+ Box(Modifier.fillMaxSize()) {
+ FilledTonalButton(
+ modifier = Modifier.align(Alignment.Center),
+ onClick = { showConfirmation = true },
+ label = { Text("Show Confirmation") }
+ )
+ }
+
+ SuccessConfirmation(show = showConfirmation, onDismissRequest = { showConfirmation = false })
+}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CurvedTextSamples.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CurvedTextSamples.kt
index 82c1cc9..8fef8a0 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CurvedTextSamples.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CurvedTextSamples.kt
@@ -54,14 +54,17 @@
@Sampled
@Composable
fun CurvedTextBottom() {
+ val backgroundColor = MaterialTheme.colorScheme.onPrimary
CurvedLayout(anchor = 90f, angularDirection = CurvedDirection.Angular.Reversed) {
- curvedComposable {
- Icon(
- Icons.Filled.Warning,
- contentDescription = "Warning",
- modifier = Modifier.size(ButtonDefaults.IconSize)
- )
+ curvedRow(CurvedModifier.background(backgroundColor, Round)) {
+ curvedComposable {
+ Icon(
+ Icons.Filled.Warning,
+ contentDescription = "Warning",
+ modifier = Modifier.size(ButtonDefaults.IconSize)
+ )
+ }
+ curvedText("Error - network lost")
}
- curvedText("Error - network lost")
}
}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/DatePickerSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/DatePickerSample.kt
new file mode 100644
index 0000000..2b25252
--- /dev/null
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/DatePickerSample.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2024 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.wear.compose.material3.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.DatePicker
+import androidx.wear.compose.material3.DatePickerType
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.Text
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+import java.time.format.FormatStyle
+
+@Sampled
+@Composable
+fun DatePickerSample() {
+ var showDatePicker by remember { mutableStateOf(true) }
+ var datePickerDate by remember { mutableStateOf(LocalDate.now()) }
+ val formatter =
+ DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
+ .withLocale(LocalConfiguration.current.locales[0])
+ if (showDatePicker) {
+ DatePicker(
+ initialDate = datePickerDate, // Initialize with last picked date on reopen
+ onDatePicked = {
+ datePickerDate = it
+ showDatePicker = false
+ }
+ )
+ } else {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center,
+ ) {
+ Button(
+ onClick = { showDatePicker = true },
+ label = { Text("Selected Date") },
+ secondaryLabel = { Text(datePickerDate.format(formatter)) },
+ icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
+ )
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun DatePickerYearMonthDaySample() {
+ var showDatePicker by remember { mutableStateOf(true) }
+ var datePickerDate by remember { mutableStateOf(LocalDate.now()) }
+ val formatter = DateTimeFormatter.ofPattern("yyyy MMM d")
+ if (showDatePicker) {
+ DatePicker(
+ initialDate = datePickerDate, // Initialize with last picked date on reopen
+ onDatePicked = {
+ datePickerDate = it
+ showDatePicker = false
+ },
+ datePickerType = DatePickerType.YearMonthDay
+ )
+ } else {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center,
+ ) {
+ Button(
+ onClick = { showDatePicker = true },
+ label = { Text("Selected Date") },
+ secondaryLabel = { Text(datePickerDate.format(formatter)) },
+ icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
+ )
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun DatePickerFromDateToDateSample() {
+ var showDatePicker by remember { mutableStateOf(true) }
+ var datePickerDate by remember { mutableStateOf(LocalDate.of(2024, 9, 2)) }
+ val formatter =
+ DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
+ .withLocale(LocalConfiguration.current.locales[0])
+ if (showDatePicker) {
+ DatePicker(
+ initialDate = datePickerDate, // Initialize with last picked date on reopen
+ onDatePicked = {
+ datePickerDate = it
+ showDatePicker = false
+ },
+ minDate = LocalDate.of(2023, 10, 15),
+ maxDate = LocalDate.of(2025, 2, 4),
+ datePickerType = DatePickerType.YearMonthDay
+ )
+ } else {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center,
+ ) {
+ Button(
+ onClick = { showDatePicker = true },
+ label = { Text("Selected Date") },
+ secondaryLabel = { Text(datePickerDate.format(formatter)) },
+ icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
+ )
+ }
+ }
+}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconButtonSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconButtonSample.kt
index e75fafa..46f3bc4 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconButtonSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconButtonSample.kt
@@ -17,11 +17,17 @@
package androidx.wear.compose.material3.samples
import androidx.annotation.Sampled
+import androidx.compose.foundation.Image
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
import androidx.wear.compose.material3.FilledIconButton
import androidx.wear.compose.material3.FilledTonalIconButton
import androidx.wear.compose.material3.Icon
@@ -88,7 +94,7 @@
@Sampled
fun IconButtonWithCornerAnimationSample() {
val interactionSource = remember { MutableInteractionSource() }
- IconButton(
+ FilledIconButton(
onClick = { /* Do something */ },
shape = IconButtonDefaults.animatedShape(interactionSource),
interactionSource = interactionSource
@@ -96,3 +102,26 @@
Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite icon")
}
}
+
+@Composable
+@Sampled
+fun IconButtonWithImageSample(
+ painter: Painter,
+ enabled: Boolean,
+ interactionSource: MutableInteractionSource? = null,
+ shape: Shape = IconButtonDefaults.shape
+) {
+ IconButton(
+ onClick = { /* Do something */ },
+ interactionSource = interactionSource,
+ shape = shape,
+ ) {
+ Image(
+ painter = painter,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier =
+ if (enabled) Modifier else Modifier.alpha(IconButtonDefaults.disabledImageOpacity)
+ )
+ }
+}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/OpenOnPhoneDialogSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/OpenOnPhoneDialogSample.kt
new file mode 100644
index 0000000..741132e
--- /dev/null
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/OpenOnPhoneDialogSample.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 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.wear.compose.material3.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.FilledTonalButton
+import androidx.wear.compose.material3.OpenOnPhoneDialog
+import androidx.wear.compose.material3.Text
+
+@Sampled
+@Composable
+fun OpenOnPhoneDialogSample() {
+ var showConfirmation by remember { mutableStateOf(false) }
+
+ Box(Modifier.fillMaxSize()) {
+ FilledTonalButton(
+ modifier = Modifier.align(Alignment.Center),
+ onClick = { showConfirmation = true },
+ label = { Text("Open on phone") }
+ )
+ }
+
+ OpenOnPhoneDialog(
+ show = showConfirmation,
+ onDismissRequest = { showConfirmation = false },
+ )
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/dialog/AlertDialogScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogScreenshotTest.kt
similarity index 95%
rename from wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/dialog/AlertDialogScreenshotTest.kt
rename to wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogScreenshotTest.kt
index 0b7d1f8..e049442 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/dialog/AlertDialogScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogScreenshotTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.wear.compose.material3.dialog
+package androidx.wear.compose.material3
import android.content.res.Configuration
import android.os.Build
@@ -41,14 +41,6 @@
import androidx.test.filters.SdkSuppress
import androidx.test.screenshot.AndroidXScreenshotTestRule
import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
-import androidx.wear.compose.material3.FilledTonalButton
-import androidx.wear.compose.material3.Icon
-import androidx.wear.compose.material3.SCREENSHOT_GOLDEN_PATH
-import androidx.wear.compose.material3.ScreenSize
-import androidx.wear.compose.material3.TEST_TAG
-import androidx.wear.compose.material3.Text
-import androidx.wear.compose.material3.goldenIdentifier
-import androidx.wear.compose.material3.setContentWithTheme
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import org.junit.Rule
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/dialog/AlertDialogTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
similarity index 96%
rename from wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/dialog/AlertDialogTest.kt
rename to wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
index 14069c6..6e59369 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/dialog/AlertDialogTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.wear.compose.material3.dialog
+package androidx.wear.compose.material3
import android.os.Build
import androidx.compose.foundation.background
@@ -39,17 +39,6 @@
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.test.filters.SdkSuppress
-import androidx.wear.compose.material3.Button
-import androidx.wear.compose.material3.LocalContentColor
-import androidx.wear.compose.material3.LocalTextAlign
-import androidx.wear.compose.material3.LocalTextMaxLines
-import androidx.wear.compose.material3.LocalTextStyle
-import androidx.wear.compose.material3.MaterialTheme
-import androidx.wear.compose.material3.TEST_TAG
-import androidx.wear.compose.material3.TestImage
-import androidx.wear.compose.material3.Text
-import androidx.wear.compose.material3.setContentWithTheme
-import androidx.wear.compose.material3.setContentWithThemeForSizeAssertions
import junit.framework.TestCase.assertEquals
import org.junit.Rule
import org.junit.Test
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ConfirmationScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ConfirmationScreenshotTest.kt
new file mode 100644
index 0000000..73e047e
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ConfirmationScreenshotTest.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import android.content.res.Configuration
+import android.os.Build
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import androidx.wear.compose.material3.ConfirmationDefaults.curvedText
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(TestParameterInjector::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+class ConfirmationScreenshotTest {
+ @get:Rule val rule = createComposeRule()
+
+ @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+ @get:Rule val testName = TestName()
+
+ @Test
+ fun confirmation_icon_linearText(@TestParameter screenSize: ScreenSize) {
+
+ rule.verifyConfirmationScreenshot(
+ testName = testName,
+ screenshotRule = screenshotRule,
+ screenSize = screenSize
+ ) { modifier ->
+ Confirmation(
+ show = true,
+ modifier = modifier,
+ onDismissRequest = {},
+ text = { Text("Your message has been sent") }
+ ) {
+ DefaultSmallIcon()
+ }
+ }
+ }
+
+ @Test
+ fun confirmation_icon_curvedText(@TestParameter screenSize: ScreenSize) {
+ rule.verifyConfirmationScreenshot(
+ testName = testName,
+ screenshotRule = screenshotRule,
+ screenSize = screenSize
+ ) { modifier ->
+ Confirmation(
+ show = true,
+ modifier = modifier,
+ onDismissRequest = {},
+ curvedText = curvedText("Confirmed")
+ ) {
+ DefaultIcon()
+ }
+ }
+ }
+
+ @Test
+ fun confirmation_icon_noText(@TestParameter screenSize: ScreenSize) {
+ rule.verifyConfirmationScreenshot(
+ testName = testName,
+ screenshotRule = screenshotRule,
+ screenSize = screenSize
+ ) { modifier ->
+ Confirmation(
+ show = true,
+ modifier = modifier,
+ onDismissRequest = {},
+ curvedText = null
+ ) {
+ DefaultIcon()
+ }
+ }
+ }
+
+ @Test
+ fun successConfirmation_icon_text(@TestParameter screenSize: ScreenSize) {
+ rule.verifyConfirmationScreenshot(
+ testName = testName,
+ screenshotRule = screenshotRule,
+ screenSize = screenSize
+ ) { modifier ->
+ SuccessConfirmation(
+ show = true,
+ modifier = modifier,
+ onDismissRequest = {},
+ curvedText = curvedText("Success")
+ )
+ }
+ }
+
+ @Test
+ fun successConfirmation_icon_noText(@TestParameter screenSize: ScreenSize) {
+ rule.verifyConfirmationScreenshot(
+ testName = testName,
+ screenshotRule = screenshotRule,
+ screenSize = screenSize
+ ) { modifier ->
+ SuccessConfirmation(
+ show = true,
+ modifier = modifier,
+ onDismissRequest = {},
+ curvedText = null
+ )
+ }
+ }
+
+ @Test
+ fun failureConfirmation_icon_text(@TestParameter screenSize: ScreenSize) {
+ rule.verifyConfirmationScreenshot(
+ testName = testName,
+ screenshotRule = screenshotRule,
+ screenSize = screenSize
+ ) { modifier ->
+ FailureConfirmation(
+ show = true,
+ modifier = modifier,
+ onDismissRequest = {},
+ curvedText = curvedText("Failure")
+ )
+ }
+ }
+
+ @Test
+ fun failureConfirmation_icon_noText(@TestParameter screenSize: ScreenSize) {
+ rule.verifyConfirmationScreenshot(
+ testName = testName,
+ screenshotRule = screenshotRule,
+ screenSize = screenSize
+ ) { modifier ->
+ FailureConfirmation(
+ show = true,
+ modifier = modifier,
+ onDismissRequest = {},
+ curvedText = null,
+ )
+ }
+ }
+
+ private fun ComposeContentTestRule.verifyConfirmationScreenshot(
+ testName: TestName,
+ screenshotRule: AndroidXScreenshotTestRule,
+ screenSize: ScreenSize,
+ content: @Composable (modifier: Modifier) -> Unit
+ ) {
+ setContentWithTheme {
+ val originalConfiguration = LocalConfiguration.current
+ val originalContext = LocalContext.current
+ val fixedScreenSizeConfiguration =
+ remember(originalConfiguration) {
+ Configuration(originalConfiguration).apply {
+ screenWidthDp = screenSize.size
+ screenHeightDp = screenSize.size
+ }
+ }
+ originalContext.resources.configuration.updateFrom(fixedScreenSizeConfiguration)
+
+ CompositionLocalProvider(
+ LocalContext provides originalContext,
+ LocalConfiguration provides fixedScreenSizeConfiguration,
+ ) {
+ content(Modifier.size(screenSize.size.dp).testTag(TEST_TAG))
+ }
+ }
+
+ onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, testName.goldenIdentifier())
+ }
+
+ @Composable
+ private fun DefaultIcon() {
+ Icon(
+ Icons.Filled.Add,
+ modifier = Modifier.size(ConfirmationDefaults.IconSize),
+ tint = MaterialTheme.colorScheme.primary,
+ contentDescription = null
+ )
+ }
+
+ @Composable
+ private fun DefaultSmallIcon() {
+ Icon(
+ Icons.Filled.Add,
+ modifier = Modifier.size(ConfirmationDefaults.SmallIconSize),
+ tint = MaterialTheme.colorScheme.primary,
+ contentDescription = null
+ )
+ }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ConfirmationTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ConfirmationTest.kt
new file mode 100644
index 0000000..31206a1
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ConfirmationTest.kt
@@ -0,0 +1,605 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertContainsColor
+import androidx.compose.testutils.assertIsEqualTo
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.test.filters.SdkSuppress
+import org.junit.Rule
+import org.junit.Test
+
+class ConfirmationTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun confirmation_linearText_supports_testtag() {
+ rule.setContentWithTheme {
+ Confirmation(
+ show = true,
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = {},
+ text = {},
+ ) {}
+ }
+ rule.onNodeWithTag(TEST_TAG).assertExists()
+ }
+
+ @Test
+ fun confirmation_curvedText_supports_testtag() {
+ rule.setContentWithTheme {
+ Confirmation(
+ show = true,
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = {},
+ curvedText = {}
+ ) {}
+ }
+ rule.onNodeWithTag(TEST_TAG).assertExists()
+ }
+
+ @Test
+ fun successConfirmation_supports_testtag() {
+ rule.setContentWithTheme {
+ SuccessConfirmation(
+ show = true,
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = {},
+ )
+ }
+ rule.onNodeWithTag(TEST_TAG).assertExists()
+ }
+
+ @Test
+ fun failureConfirmation_supports_testtag() {
+ rule.setContentWithTheme {
+ FailureConfirmation(
+ show = true,
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = {},
+ )
+ }
+ rule.onNodeWithTag(TEST_TAG).assertExists()
+ }
+
+ @Test
+ fun confirmation_linearText_supports_swipeToDismiss() {
+ rule.setContentWithTheme {
+ var showDialog by remember { mutableStateOf(true) }
+ Confirmation(
+ modifier = Modifier.testTag(TEST_TAG),
+ text = {},
+ onDismissRequest = { showDialog = false },
+ show = showDialog
+ ) {}
+ }
+
+ rule.onNodeWithTag(TEST_TAG).performTouchInput { swipeRight() }
+ rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun confirmation_curvedText_supports_swipeToDismiss() {
+ rule.setContentWithTheme {
+ var showDialog by remember { mutableStateOf(true) }
+ Confirmation(
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = { showDialog = false },
+ show = showDialog,
+ curvedText = {}
+ ) {}
+ }
+
+ rule.onNodeWithTag(TEST_TAG).performTouchInput { swipeRight() }
+ rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun successConfirmation_supports_swipeToDismiss() {
+ rule.mainClock.autoAdvance = false
+ rule.setContentWithTheme {
+ var showDialog by remember { mutableStateOf(true) }
+ SuccessConfirmation(
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = { showDialog = false },
+ show = showDialog,
+ )
+ }
+ // Advancing time so that animation will finish its motion.
+ rule.mainClock.advanceTimeBy(1000)
+ rule.onNodeWithTag(TEST_TAG).performTouchInput { swipeRight() }
+ rule.mainClock.advanceTimeBy(1000)
+ rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun failureConfirmation_supports_swipeToDismiss() {
+ rule.mainClock.autoAdvance = false
+ rule.setContentWithTheme {
+ var showDialog by remember { mutableStateOf(true) }
+ FailureConfirmation(
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = { showDialog = false },
+ show = showDialog,
+ )
+ }
+ // Advancing time so that animation will finish its motion.
+ rule.mainClock.advanceTimeBy(1000)
+ rule.onNodeWithTag(TEST_TAG).performTouchInput { swipeRight() }
+ rule.mainClock.advanceTimeBy(1000)
+ rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun hides_confirmation_linearText_when_show_false() {
+ rule.setContentWithTheme {
+ Confirmation(
+ show = false,
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = {},
+ text = {},
+ ) {}
+ }
+ rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun hides_confirmation_curvedText_when_show_false() {
+ rule.setContentWithTheme {
+ Confirmation(
+ show = false,
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = {},
+ curvedText = {}
+ ) {}
+ }
+ rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun hides_successConfirmation_when_show_false() {
+ rule.setContentWithTheme {
+ SuccessConfirmation(
+ show = false,
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = {},
+ )
+ }
+ rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun hides_failureConfirmation_when_show_false() {
+ rule.setContentWithTheme {
+ FailureConfirmation(
+ show = false,
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = {},
+ )
+ }
+ rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun confirmation_displays_icon_with_linearText() {
+ rule.setContentWithTheme {
+ Confirmation(
+ text = { Text("Text", modifier = Modifier.testTag(TextTestTag)) },
+ onDismissRequest = {},
+ show = true
+ ) {
+ TestImage(IconTestTag)
+ }
+ }
+ rule.onNodeWithTag(IconTestTag).assertExists()
+ rule.onNodeWithTag(TextTestTag).assertExists()
+ }
+
+ @Test
+ fun confirmation_displays_icon_with_curvedText() {
+ rule.setContentWithTheme {
+ Confirmation(
+ onDismissRequest = {},
+ show = true,
+ curvedText = { curvedText(CurvedText) }
+ ) {
+ TestImage(IconTestTag)
+ }
+ }
+ rule.onNodeWithTag(IconTestTag).assertExists()
+ rule.onNodeWithContentDescription(CurvedText).assertExists()
+ }
+
+ @Test
+ fun successConfirmation_displays_icon_with_text() {
+ rule.setContentWithTheme {
+ SuccessConfirmation(
+ onDismissRequest = {},
+ show = true,
+ curvedText = ConfirmationDefaults.curvedText(CurvedText)
+ ) {
+ TestImage(IconTestTag)
+ }
+ }
+ rule.onNodeWithTag(IconTestTag).assertExists()
+ rule.onNodeWithContentDescription(CurvedText).assertExists()
+ }
+
+ @Test
+ fun failureConfirmation_displays_icon_with_text() {
+ rule.setContentWithTheme {
+ FailureConfirmation(
+ onDismissRequest = {},
+ show = true,
+ curvedText = ConfirmationDefaults.curvedText(CurvedText)
+ ) {
+ TestImage(IconTestTag)
+ }
+ }
+ rule.onNodeWithTag(IconTestTag).assertExists()
+ rule.onNodeWithContentDescription(CurvedText).assertExists()
+ }
+
+ @Test
+ fun confirmation_linearText_dismissed_after_timeout() {
+ var dismissed = false
+ rule.mainClock.autoAdvance = false
+ rule.setContentWithTheme {
+ Confirmation(text = {}, onDismissRequest = { dismissed = true }, show = true) {}
+ }
+ // Timeout longer than default confirmation duration
+ rule.mainClock.advanceTimeBy(ConfirmationDefaults.ConfirmationDurationMillis + 1000)
+ assert(dismissed)
+ }
+
+ @Test
+ fun confirmation_curvedText_dismissed_after_timeout() {
+ var dismissed = false
+ rule.mainClock.autoAdvance = false
+ rule.setContentWithTheme {
+ Confirmation(onDismissRequest = { dismissed = true }, show = true, curvedText = {}) {}
+ }
+ // Timeout longer than default confirmation duration
+ rule.mainClock.advanceTimeBy(ConfirmationDefaults.ConfirmationDurationMillis + 1000)
+ assert(dismissed)
+ }
+
+ @Test
+ fun successConfirmation_dismissed_after_timeout() {
+ var dismissed = false
+ rule.mainClock.autoAdvance = false
+ rule.setContentWithTheme {
+ SuccessConfirmation(
+ onDismissRequest = { dismissed = true },
+ show = true,
+ )
+ }
+ // Timeout longer than default confirmation duration
+ rule.mainClock.advanceTimeBy(ConfirmationDefaults.ConfirmationDurationMillis + 1000)
+ assert(dismissed)
+ }
+
+ @Test
+ fun failureConfirmation_dismissed_after_timeout() {
+ var dismissed = false
+ rule.mainClock.autoAdvance = false
+ rule.setContentWithTheme {
+ FailureConfirmation(
+ onDismissRequest = { dismissed = true },
+ show = true,
+ )
+ }
+ // Timeout longer than default confirmation duration
+ rule.mainClock.advanceTimeBy(ConfirmationDefaults.ConfirmationDurationMillis + 1000)
+ assert(dismissed)
+ }
+
+ @Test
+ fun confirmation_linearText_positioning() {
+ rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+ Confirmation(
+ show = true,
+ text = {
+ Text(
+ "Title",
+ modifier = Modifier.testTag(TextTestTag),
+ textAlign = TextAlign.Center
+ )
+ },
+ onDismissRequest = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ ) {
+ TestIcon(Modifier.testTag(IconTestTag))
+ }
+ }
+
+ // Calculating the center of the icon
+ val iconCenter =
+ rule.onNodeWithTag(IconTestTag).getUnclippedBoundsInRoot().run { (top + bottom) / 2 }
+ val textTop = rule.onNodeWithTag(TextTestTag).getUnclippedBoundsInRoot().top
+
+ // Stepping down half of the container height with vertical content padding
+ textTop.assertIsEqualTo(
+ iconCenter +
+ ConfirmationDefaults.ConfirmationIconContainerSmallSize / 2 +
+ ConfirmationDefaults.LinearContentSpacing
+ )
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun confirmation_linearText_correct_colors() {
+ var expectedIconColor: Color = Color.Unspecified
+ var expectedIconContainerColor: Color = Color.Unspecified
+ var expectedTextColor: Color = Color.Unspecified
+
+ rule.setContentWithTheme {
+ Confirmation(
+ onDismissRequest = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ show = true,
+ text = { Text("Text") },
+ ) {
+ TestIcon(Modifier.testTag(IconTestTag))
+ }
+ expectedIconColor = MaterialTheme.colorScheme.primary
+ expectedIconContainerColor = MaterialTheme.colorScheme.onPrimary
+ expectedTextColor = MaterialTheme.colorScheme.onBackground
+ }
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedIconColor)
+
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertContainsColor(expectedIconContainerColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedTextColor)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun confirmation_curvedText_correct_colors() {
+ var expectedIconColor: Color = Color.Unspecified
+ var expectedIconContainerColor: Color = Color.Unspecified
+ var expectedTextColor: Color = Color.Unspecified
+ rule.setContentWithTheme {
+ Confirmation(
+ onDismissRequest = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ show = true,
+ curvedText = ConfirmationDefaults.curvedText(CurvedText)
+ ) {
+ TestIcon(Modifier.testTag(IconTestTag))
+ }
+ expectedIconColor = MaterialTheme.colorScheme.primary
+ expectedIconContainerColor = MaterialTheme.colorScheme.onPrimary
+ expectedTextColor = MaterialTheme.colorScheme.onBackground
+ }
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedIconColor)
+
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertContainsColor(expectedIconContainerColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedTextColor)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun successConfirmation_correct_colors() {
+ var expectedIconColor: Color = Color.Unspecified
+ var expectedIconContainerColor: Color = Color.Unspecified
+ var expectedTextColor: Color = Color.Unspecified
+
+ rule.setContentWithTheme {
+ SuccessConfirmation(
+ onDismissRequest = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ show = true,
+ )
+ expectedIconColor = MaterialTheme.colorScheme.primary
+ expectedIconContainerColor = MaterialTheme.colorScheme.onPrimary
+ expectedTextColor = MaterialTheme.colorScheme.onBackground
+ }
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedIconColor)
+
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertContainsColor(expectedIconContainerColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedTextColor)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun failureConfirmation_correct_colors() {
+ var expectedIconColor: Color = Color.Unspecified
+ var expectedIconContainerColor: Color = Color.Unspecified
+ var expectedTextColor: Color = Color.Unspecified
+ val backgroundColor = Color.Black
+ rule.setContentWithTheme {
+ FailureConfirmation(
+ onDismissRequest = {},
+ modifier = Modifier.testTag(TEST_TAG).background(backgroundColor),
+ show = true,
+ ) {
+ TestIcon(Modifier.testTag(IconTestTag))
+ }
+ expectedIconColor = MaterialTheme.colorScheme.errorContainer
+ // As we have .8 alpha, we have to merge this color with background
+ expectedIconContainerColor =
+ MaterialTheme.colorScheme.onErrorContainer.copy(.8f).compositeOver(backgroundColor)
+ expectedTextColor = MaterialTheme.colorScheme.onBackground
+ }
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedIconColor)
+
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertContainsColor(expectedIconContainerColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedTextColor)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun confirmation_linearText_custom_colors() {
+ val customIconColor: Color = Color.Red
+ val customIconContainerColor: Color = Color.Green
+ val customTextColor: Color = Color.Blue
+
+ rule.setContentWithTheme {
+ Confirmation(
+ onDismissRequest = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ show = true,
+ text = { Text("Text") },
+ colors =
+ ConfirmationDefaults.confirmationColors(
+ iconColor = customIconColor,
+ iconContainerColor = customIconContainerColor,
+ textColor = customTextColor
+ )
+ ) {
+ TestIcon(Modifier.testTag(IconTestTag))
+ }
+ }
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customIconColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customIconContainerColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customTextColor)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun confirmation_curvedText_custom_colors() {
+ val customIconColor: Color = Color.Red
+ val customIconContainerColor: Color = Color.Green
+ val customTextColor: Color = Color.Blue
+
+ rule.setContentWithTheme {
+ Confirmation(
+ onDismissRequest = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ show = true,
+ colors =
+ ConfirmationDefaults.confirmationColors(
+ iconColor = customIconColor,
+ iconContainerColor = customIconContainerColor,
+ textColor = customTextColor
+ ),
+ curvedText = ConfirmationDefaults.curvedText(CurvedText)
+ ) {
+ TestIcon(Modifier.testTag(IconTestTag))
+ }
+ }
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customIconColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customIconContainerColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customTextColor)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun successConfirmation_curvedText_custom_colors() {
+ val customIconColor: Color = Color.Red
+ val customIconContainerColor: Color = Color.Green
+ val customTextColor: Color = Color.Blue
+
+ rule.setContentWithTheme {
+ SuccessConfirmation(
+ onDismissRequest = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ show = true,
+ colors =
+ ConfirmationDefaults.successColors(
+ iconColor = customIconColor,
+ iconContainerColor = customIconContainerColor,
+ textColor = customTextColor
+ ),
+ ) {
+ TestIcon(Modifier.testTag(IconTestTag))
+ }
+ }
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customIconColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customIconContainerColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customTextColor)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun failureConfirmation_curvedText_custom_colors() {
+ val customIconColor: Color = Color.Red
+ val customIconContainerColor: Color = Color.Green
+ val customTextColor: Color = Color.Blue
+
+ rule.setContentWithTheme {
+ FailureConfirmation(
+ onDismissRequest = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ show = true,
+ colors =
+ ConfirmationDefaults.failureColors(
+ iconColor = customIconColor,
+ iconContainerColor = customIconContainerColor,
+ textColor = customTextColor
+ ),
+ ) {
+ TestIcon(Modifier.testTag(IconTestTag))
+ }
+ }
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customIconColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customIconContainerColor)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customTextColor)
+ }
+}
+
+private const val IconTestTag = "icon"
+private const val TextTestTag = "text"
+private const val CurvedText = "CurvedText"
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerScreenshotTest.kt
new file mode 100644
index 0000000..7f334a8
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerScreenshotTest.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import android.content.res.Configuration
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithContentDescription
+import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+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.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import androidx.wear.compose.material3.internal.Strings
+import java.time.LocalDate
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class DatePickerScreenshotTest {
+ @get:Rule val rule = createComposeRule()
+
+ @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+ @get:Rule val testName = TestName()
+
+ @Test
+ fun datePicker_dayMonthYear_ltr() {
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ layoutDirection = LayoutDirection.Ltr,
+ content = { DatePickerDayMonthYear() }
+ )
+ }
+
+ @Test
+ fun datePicker_dayMonthYear_rtl() {
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ layoutDirection = LayoutDirection.Rtl,
+ content = { DatePickerDayMonthYear() }
+ )
+ }
+
+ @Test
+ fun datePicker_dayMonthYear_largeScreen() {
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ isLargeScreen = true,
+ content = { DatePickerDayMonthYear() }
+ )
+ }
+
+ @Test
+ fun datePicker_dayMonthYear_monthFocused_ltr() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ layoutDirection = LayoutDirection.Ltr,
+ action = { rule.nextButton().performClick() },
+ content = { DatePickerDayMonthYear() }
+ )
+
+ @Test
+ fun datePicker_dayMonthYear_monthFocused_rtl() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ layoutDirection = LayoutDirection.Rtl,
+ action = { rule.nextButton().performClick() },
+ content = { DatePickerDayMonthYear() }
+ )
+
+ @Test
+ fun datePicker_dayMonthYear_monthFocused_largeScreen() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ isLargeScreen = true,
+ action = { rule.nextButton().performClick() },
+ content = { DatePickerDayMonthYear() }
+ )
+
+ @Test
+ fun datePicker_dayMonthYear_yearFocused_ltr() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ layoutDirection = LayoutDirection.Ltr,
+ action = {
+ rule.nextButton().performClick()
+ rule.nextButton().performClick()
+ },
+ content = { DatePickerDayMonthYear() }
+ )
+
+ @Test
+ fun datePicker_dayMonthYear_yearFocused_rtl() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ layoutDirection = LayoutDirection.Rtl,
+ action = {
+ rule.nextButton().performClick()
+ rule.nextButton().performClick()
+ },
+ content = { DatePickerDayMonthYear() }
+ )
+
+ @Test
+ fun datePicker_dayMonthYear_yearFocused_largeScreen() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ isLargeScreen = true,
+ action = {
+ rule.nextButton().performClick()
+ rule.nextButton().performClick()
+ },
+ content = { DatePickerDayMonthYear() }
+ )
+
+ @Test
+ fun datePicker_monthDayYear_ltr() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ layoutDirection = LayoutDirection.Ltr,
+ content = { DatePickerMonthDayYear() }
+ )
+
+ @Test
+ fun datePicker_monthDayYear_rtl() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ layoutDirection = LayoutDirection.Rtl,
+ content = { DatePickerMonthDayYear() }
+ )
+
+ @Test
+ fun datePicker_monthDayYear_largeScreen() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ isLargeScreen = true,
+ content = { DatePickerMonthDayYear() }
+ )
+
+ @Test
+ fun datePicker_yearMonthDay_ltr() {
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ layoutDirection = LayoutDirection.Ltr,
+ content = { DatePickerYearMonthDay() }
+ )
+ }
+
+ @Test
+ fun datePicker_yearMonthDay_rtl() {
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ layoutDirection = LayoutDirection.Rtl,
+ content = { DatePickerYearMonthDay() }
+ )
+ }
+
+ @Test
+ fun datePicker_yearMonthDay_largeScreen() {
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ isLargeScreen = true,
+ content = { DatePickerYearMonthDay() }
+ )
+ }
+
+ @Test
+ fun datePicker_yearMonthDay_year_does_not_repeat() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ content = {
+ DatePicker(
+ onDatePicked = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ datePickerType = DatePickerType.YearMonthDay,
+ initialDate =
+ LocalDate.of(/* year= */ 2024, /* month= */ 9, /* dayOfMonth= */ 15),
+ minDate = LocalDate.of(/* year= */ 2024, /* month= */ 8, /* dayOfMonth= */ 15),
+ maxDate = LocalDate.of(/* year= */ 2024, /* month= */ 10, /* dayOfMonth= */ 15),
+ )
+ }
+ )
+
+ @Test
+ fun datePicker_monthYearDay_month_does_not_repeat() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ content = {
+ DatePicker(
+ onDatePicked = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ datePickerType = DatePickerType.MonthDayYear,
+ initialDate =
+ LocalDate.of(/* year= */ 2024, /* month= */ 1, /* dayOfMonth= */ 15),
+ minDate = LocalDate.of(/* year= */ 2024, /* month= */ 1, /* dayOfMonth= */ 1),
+ maxDate = LocalDate.of(/* year= */ 2024, /* month= */ 2, /* dayOfMonth= */ 15),
+ )
+ }
+ )
+
+ @Test
+ fun datePicker_dayMonthYear_day_does_not_repeat() =
+ rule.verifyDatePickerScreenshot(
+ methodName = testName.methodName,
+ screenshotRule = screenshotRule,
+ content = {
+ DatePicker(
+ onDatePicked = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ datePickerType = DatePickerType.DayMonthYear,
+ initialDate =
+ LocalDate.of(/* year= */ 2024, /* month= */ 2, /* dayOfMonth= */ 1),
+ maxDate = LocalDate.of(/* year= */ 2024, /* month= */ 2, /* dayOfMonth= */ 1),
+ )
+ }
+ )
+
+ @Composable
+ private fun DatePickerDayMonthYear() {
+ DatePicker(
+ onDatePicked = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ datePickerType = DatePickerType.DayMonthYear,
+ initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 8, /* dayOfMonth= */ 15)
+ )
+ }
+
+ @Composable
+ private fun DatePickerMonthDayYear() {
+ DatePicker(
+ onDatePicked = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ datePickerType = DatePickerType.MonthDayYear,
+ initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 8, /* dayOfMonth= */ 15)
+ )
+ }
+
+ @Composable
+ private fun DatePickerYearMonthDay() {
+ DatePicker(
+ onDatePicked = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ datePickerType = DatePickerType.YearMonthDay,
+ initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 8, /* dayOfMonth= */ 15)
+ )
+ }
+
+ private fun SemanticsNodeInteractionsProvider.nextButton(): SemanticsNodeInteraction =
+ onAllNodesWithContentDescription(
+ InstrumentationRegistry.getInstrumentation()
+ .context
+ .resources
+ .getString(Strings.PickerNextButtonContentDescription.value)
+ )
+ .onFirst()
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun ComposeContentTestRule.verifyDatePickerScreenshot(
+ methodName: String,
+ screenshotRule: AndroidXScreenshotTestRule,
+ testTag: String = TEST_TAG,
+ layoutDirection: LayoutDirection = LayoutDirection.Ltr,
+ isLargeScreen: Boolean = false,
+ action: (() -> Unit)? = null,
+ content: @Composable () -> Unit
+ ) {
+ val screenSizeDp = if (isLargeScreen) SCREENSHOT_SIZE_LARGE else SCREENSHOT_SIZE
+ setContentWithTheme {
+ val originalConfiguration = LocalConfiguration.current
+ val fixedScreenSizeConfiguration =
+ remember(originalConfiguration) {
+ Configuration(originalConfiguration).apply {
+ screenWidthDp = screenSizeDp
+ screenHeightDp = screenSizeDp
+ }
+ }
+ CompositionLocalProvider(
+ LocalLayoutDirection provides layoutDirection,
+ LocalConfiguration provides fixedScreenSizeConfiguration
+ ) {
+ Box(
+ modifier =
+ Modifier.size(screenSizeDp.dp)
+ .background(MaterialTheme.colorScheme.background)
+ ) {
+ content()
+ }
+ }
+ }
+ action?.let { it() }
+
+ onNodeWithTag(testTag).captureToImage().assertAgainstGolden(screenshotRule, methodName)
+ }
+}
+
+private const val SCREENSHOT_SIZE = 192
+private const val SCREENSHOT_SIZE_LARGE = 228
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerTest.kt
new file mode 100644
index 0000000..c0a2138
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerTest.kt
@@ -0,0 +1,517 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import android.content.res.Resources
+import android.os.Build
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithContentDescription
+import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performScrollToIndex
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.wear.compose.material3.internal.Strings
+import androidx.wear.compose.material3.samples.DatePickerSample
+import androidx.wear.compose.material3.samples.DatePickerYearMonthDaySample
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class DatePickerTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun supports_testtag() {
+ rule.setContentWithTheme {
+ DatePicker(
+ initialDate = LocalDate.now(),
+ onDatePicked = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assertExists()
+ }
+
+ @Test
+ fun samples_build() {
+ rule.setContentWithTheme {
+ DatePickerSample()
+ DatePickerYearMonthDaySample()
+ }
+ }
+
+ @Test
+ fun dayMonthYear_initial_state() {
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 8, /* dayOfMonth= */ 15)
+ rule.setContentWithTheme {
+ DatePicker(
+ onDatePicked = {},
+ initialDate = initialDate,
+ datePickerType = DatePickerType.DayMonthYear
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.dayOfMonth,
+ selectionMode = SelectionMode.Day
+ )
+ .assertIsFocused()
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.monthValue,
+ selectionMode = SelectionMode.Month
+ )
+ .assertIsDisplayed()
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .assertIsDisplayed()
+ rule.nextButton().assertIsDisplayed()
+ rule.confirmButton().assertDoesNotExist()
+ }
+
+ @Test
+ fun monthDayYear_initial_state() {
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 2, /* dayOfMonth= */ 29)
+ rule.setContentWithTheme {
+ DatePicker(
+ onDatePicked = {},
+ initialDate = initialDate,
+ datePickerType = DatePickerType.MonthDayYear
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.monthValue,
+ selectionMode = SelectionMode.Month
+ )
+ .assertIsFocused()
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.dayOfMonth,
+ selectionMode = SelectionMode.Day
+ )
+ .assertIsDisplayed()
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .assertIsDisplayed()
+ rule.nextButton().assertIsDisplayed()
+ rule.confirmButton().assertDoesNotExist()
+ }
+
+ @Test
+ fun yearMonthDay_initial_state() {
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 12, /* dayOfMonth= */ 31)
+ rule.setContentWithTheme {
+ DatePicker(
+ onDatePicked = {},
+ initialDate = initialDate,
+ datePickerType = DatePickerType.YearMonthDay
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .assertIsFocused()
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.monthValue,
+ selectionMode = SelectionMode.Month
+ )
+ .assertIsDisplayed()
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.dayOfMonth,
+ selectionMode = SelectionMode.Day
+ )
+ .assertIsDisplayed()
+ rule.nextButton().assertIsDisplayed()
+ rule.confirmButton().assertDoesNotExist()
+ }
+
+ @Test
+ fun dayMonthYear_switch_to_month() {
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 8, /* dayOfMonth= */ 15)
+ rule.setContentWithTheme {
+ DatePicker(
+ initialDate = initialDate,
+ onDatePicked = {},
+ datePickerType = DatePickerType.DayMonthYear
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.monthValue,
+ selectionMode = SelectionMode.Month
+ )
+ .performClick()
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.monthValue,
+ selectionMode = SelectionMode.Month
+ )
+ .assertIsFocused()
+ }
+
+ @Test
+ fun dayMonthYear_switch_to_year() {
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 8, /* dayOfMonth= */ 15)
+ rule.setContentWithTheme {
+ DatePicker(
+ initialDate = initialDate,
+ onDatePicked = {},
+ datePickerType = DatePickerType.DayMonthYear
+ )
+ }
+
+ rule.nextButton().performClick()
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .performClick()
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .assertIsFocused()
+ }
+
+ @Test
+ fun date_picked() {
+ lateinit var pickedDate: LocalDate
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 8, /* dayOfMonth= */ 15)
+ val expectedDate = LocalDate.of(/* year= */ 2025, /* month= */ 2, /* dayOfMonth= */ 5)
+ rule.setContentWithTheme {
+ DatePicker(
+ onDatePicked = { pickedDate = it },
+ initialDate = initialDate,
+ datePickerType = DatePickerType.DayMonthYear
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.dayOfMonth,
+ selectionMode = SelectionMode.Day
+ )
+ .performScrollToIndex(expectedDate.dayOfMonth - 1)
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.monthValue,
+ selectionMode = SelectionMode.Month
+ )
+ .performScrollToIndex(expectedDate.monthValue - 1)
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .performScrollToIndex(expectedDate.year - 1900)
+ rule.confirmButton().performClick()
+ rule.waitForIdle()
+
+ assertThat(pickedDate).isEqualTo(expectedDate)
+ }
+
+ @Test
+ fun date_picked_between_fromDate_and_toDate() {
+ lateinit var pickedDate: LocalDate
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 8, /* dayOfMonth= */ 15)
+ val expectedDate = LocalDate.of(/* year= */ 2025, /* month= */ 2, /* dayOfMonth= */ 5)
+ rule.setContentWithTheme {
+ DatePicker(
+ onDatePicked = { pickedDate = it },
+ initialDate = initialDate,
+ datePickerType = DatePickerType.DayMonthYear,
+ minDate = LocalDate.of(/* year= */ 2024, /* month= */ 1, /* dayOfMonth= */ 1),
+ maxDate = LocalDate.of(/* year= */ 2025, /* month= */ 12, /* dayOfMonth= */ 6)
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.dayOfMonth,
+ selectionMode = SelectionMode.Day
+ )
+ .performScrollToIndex(expectedDate.dayOfMonth - 1)
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.monthValue,
+ selectionMode = SelectionMode.Month
+ )
+ .performScrollToIndex(expectedDate.monthValue - 1)
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .performScrollToIndex(expectedDate.year - 1900)
+ rule.confirmButton().performClick()
+ rule.waitForIdle()
+
+ assertThat(pickedDate).isEqualTo(expectedDate)
+ }
+
+ @Test
+ fun auto_scroll_day_to_fromDate() {
+ lateinit var pickedDate: LocalDate
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 9, /* dayOfMonth= */ 6)
+ val expectedDate = LocalDate.of(/* year= */ 2024, /* month= */ 8, /* dayOfMonth= */ 15)
+ rule.setContentWithTheme {
+ DatePicker(
+ onDatePicked = { pickedDate = it },
+ initialDate = initialDate,
+ datePickerType = DatePickerType.YearMonthDay,
+ minDate = LocalDate.of(/* year= */ 2024, /* month= */ 8, /* dayOfMonth= */ 15),
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.monthValue,
+ selectionMode = SelectionMode.Month
+ )
+ .performScrollToIndex(0)
+ rule.nextButton().performClick()
+ rule.confirmButton().performClick()
+ rule.waitForIdle()
+
+ assertThat(pickedDate).isEqualTo(expectedDate)
+ }
+
+ @Test
+ fun auto_scroll_month_to_fromDate_month() {
+ lateinit var pickedDate: LocalDate
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 7, /* dayOfMonth= */ 15)
+ val expectedDate = LocalDate.of(/* year= */ 2023, /* month= */ 8, /* dayOfMonth= */ 15)
+ rule.setContentWithTheme {
+ DatePicker(
+ onDatePicked = { pickedDate = it },
+ initialDate = initialDate,
+ datePickerType = DatePickerType.YearMonthDay,
+ minDate = LocalDate.of(/* year= */ 2023, /* month= */ 8, /* dayOfMonth= */ 5),
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .performScrollToIndex(0)
+ rule.nextButton().performClick()
+ rule.confirmButton().performClick()
+ rule.waitForIdle()
+
+ assertThat(pickedDate).isEqualTo(expectedDate)
+ }
+
+ @Test
+ fun auto_scroll_month_and_day_to_fromDate() {
+ lateinit var pickedDate: LocalDate
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 9, /* dayOfMonth= */ 6)
+ val expectedDate = LocalDate.of(/* year= */ 2023, /* month= */ 10, /* dayOfMonth= */ 15)
+ rule.setContentWithTheme {
+ DatePicker(
+ onDatePicked = { pickedDate = it },
+ initialDate = initialDate,
+ datePickerType = DatePickerType.YearMonthDay,
+ minDate = LocalDate.of(/* year= */ 2023, /* month= */ 10, /* dayOfMonth= */ 15),
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .performScrollToIndex(0)
+ rule.nextButton().performClick()
+ rule.confirmButton().performClick()
+ rule.waitForIdle()
+
+ assertThat(pickedDate).isEqualTo(expectedDate)
+ }
+
+ @Test
+ fun auto_scroll_month_to_toDate_month() {
+ lateinit var pickedDate: LocalDate
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 9, /* dayOfMonth= */ 2)
+ val expectedDate = LocalDate.of(/* year= */ 2025, /* month= */ 2, /* dayOfMonth= */ 2)
+ rule.setContentWithTheme {
+ DatePicker(
+ onDatePicked = { pickedDate = it },
+ initialDate = initialDate,
+ datePickerType = DatePickerType.YearMonthDay,
+ maxDate = LocalDate.of(/* year= */ 2025, /* month= */ 2, /* dayOfMonth= */ 4)
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .performScrollToIndex(2025 - 1900)
+ rule.nextButton().performClick()
+ rule.confirmButton().performClick()
+ rule.waitForIdle()
+
+ assertThat(pickedDate).isEqualTo(expectedDate)
+ }
+
+ @Test
+ fun auto_scroll_day_to_toDate() {
+ lateinit var pickedDate: LocalDate
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 2, /* dayOfMonth= */ 12)
+ val expectedDate = LocalDate.of(/* year= */ 2025, /* month= */ 2, /* dayOfMonth= */ 4)
+ rule.setContentWithTheme {
+ DatePicker(
+ onDatePicked = { pickedDate = it },
+ initialDate = initialDate,
+ datePickerType = DatePickerType.YearMonthDay,
+ maxDate = LocalDate.of(/* year= */ 2025, /* month= */ 2, /* dayOfMonth= */ 4)
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .performScrollToIndex(2025 - 1900)
+ rule.nextButton().performClick()
+ rule.confirmButton().performClick()
+ rule.waitForIdle()
+
+ assertThat(pickedDate).isEqualTo(expectedDate)
+ }
+
+ @Test
+ fun auto_scroll_month_and_day_to_toDate() {
+ lateinit var pickedDate: LocalDate
+ val initialDate = LocalDate.of(/* year= */ 2024, /* month= */ 9, /* dayOfMonth= */ 10)
+ val expectedDate = LocalDate.of(/* year= */ 2025, /* month= */ 2, /* dayOfMonth= */ 4)
+ rule.setContentWithTheme {
+ DatePicker(
+ onDatePicked = { pickedDate = it },
+ initialDate = initialDate,
+ datePickerType = DatePickerType.YearMonthDay,
+ maxDate = LocalDate.of(/* year= */ 2025, /* month= */ 2, /* dayOfMonth= */ 4)
+ )
+ }
+
+ rule
+ .onNodeWithDateValue(
+ selectedValue = initialDate.year,
+ selectionMode = SelectionMode.Year
+ )
+ .performScrollToIndex(2025 - 1900)
+ rule.nextButton().performClick()
+ rule.confirmButton().performClick()
+ rule.waitForIdle()
+
+ assertThat(pickedDate).isEqualTo(expectedDate)
+ }
+
+ private fun SemanticsNodeInteractionsProvider.onNodeWithDateValue(
+ selectedValue: Int,
+ selectionMode: SelectionMode,
+ ): SemanticsNodeInteraction =
+ onAllNodesWithContentDescription(
+ if (selectionMode == SelectionMode.Month) {
+ monthNames[(selectedValue - 1) % 12]
+ } else {
+ contentDescriptionForValue(
+ InstrumentationRegistry.getInstrumentation().context.resources,
+ selectedValue,
+ selectionMode.contentDescriptionResource
+ )
+ }
+ )
+ .onFirst()
+
+ private fun SemanticsNodeInteractionsProvider.confirmButton(): SemanticsNodeInteraction =
+ onAllNodesWithContentDescription(
+ InstrumentationRegistry.getInstrumentation()
+ .context
+ .resources
+ .getString(Strings.PickerConfirmButtonContentDescription.value)
+ )
+ .onFirst()
+
+ private fun SemanticsNodeInteractionsProvider.nextButton(): SemanticsNodeInteraction =
+ onAllNodesWithContentDescription(
+ InstrumentationRegistry.getInstrumentation()
+ .context
+ .resources
+ .getString(Strings.PickerNextButtonContentDescription.value)
+ )
+ .onFirst()
+
+ private fun contentDescriptionForValue(
+ resources: Resources,
+ selectedValue: Int,
+ contentDescriptionResource: Strings,
+ ): String = "${resources.getString(contentDescriptionResource.value)}, $selectedValue"
+
+ private enum class SelectionMode(val contentDescriptionResource: Strings) {
+ Day(Strings.DatePickerDay),
+ Month(Strings.DatePickerMonth),
+ Year(Strings.DatePickerYear),
+ }
+
+ private val monthNames: List<String>
+ get() {
+ val monthFormatter = DateTimeFormatter.ofPattern("MMMM")
+ val months = 1..12
+ return months.map { LocalDate.of(2022, it, 1).format(monthFormatter) }
+ }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
index 188a8f8..9c0a3f0 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
@@ -618,7 +618,7 @@
private fun ComposeContentTestRule.verifyIconToggleButtonColors(
status: Status,
checked: Boolean,
- colors: @Composable () -> ToggleButtonColors,
+ colors: @Composable () -> IconToggleButtonColors,
containerColor: @Composable () -> Color,
contentColor: @Composable () -> Color,
) {
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogScreenshotTest.kt
new file mode 100644
index 0000000..74568a6
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogScreenshotTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import android.content.res.Configuration
+import android.os.Build
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(TestParameterInjector::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+class OpenOnPhoneDialogScreenshotTest {
+ @get:Rule val rule = createComposeRule()
+
+ @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+ @get:Rule val testName = TestName()
+
+ @Test
+ fun openOnPhone_50_percent_progress(@TestParameter screenSize: ScreenSize) {
+ rule.verifyOpenOnPhoneScreenshot(
+ testName = testName,
+ screenshotRule = screenshotRule,
+ advanceTimeBy = OpenOnPhoneDialogDefaults.DurationMillis / 2,
+ screenSize = screenSize
+ )
+ }
+
+ @Test
+ fun openOnPhone_100_percent_progress(@TestParameter screenSize: ScreenSize) {
+ rule.verifyOpenOnPhoneScreenshot(
+ testName = testName,
+ screenshotRule = screenshotRule,
+ advanceTimeBy = OpenOnPhoneDialogDefaults.DurationMillis,
+ screenSize = screenSize
+ )
+ }
+
+ private fun ComposeContentTestRule.verifyOpenOnPhoneScreenshot(
+ testName: TestName,
+ screenshotRule: AndroidXScreenshotTestRule,
+ screenSize: ScreenSize,
+ advanceTimeBy: Long,
+ ) {
+ rule.mainClock.autoAdvance = false
+ setContentWithTheme {
+ val originalConfiguration = LocalConfiguration.current
+ val originalContext = LocalContext.current
+ val fixedScreenSizeConfiguration =
+ remember(originalConfiguration) {
+ Configuration(originalConfiguration).apply {
+ screenWidthDp = screenSize.size
+ screenHeightDp = screenSize.size
+ }
+ }
+ originalContext.resources.configuration.updateFrom(fixedScreenSizeConfiguration)
+
+ CompositionLocalProvider(
+ LocalContext provides originalContext,
+ LocalConfiguration provides fixedScreenSizeConfiguration,
+ ) {
+ OpenOnPhoneDialog(
+ show = true,
+ modifier = Modifier.size(screenSize.size.dp).testTag(TEST_TAG),
+ onDismissRequest = {},
+ )
+ }
+ }
+
+ rule.mainClock.advanceTimeBy(advanceTimeBy)
+ onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, testName.goldenIdentifier())
+ }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogTest.kt
new file mode 100644
index 0000000..c5de9c1
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import android.os.Build
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertContainsColor
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeRight
+import androidx.test.filters.SdkSuppress
+import org.junit.Rule
+import org.junit.Test
+
+class OpenOnPhoneDialogTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun openOnPhone_supports_testtag() {
+ rule.setContentWithTheme {
+ OpenOnPhoneDialog(
+ show = true,
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = {},
+ )
+ }
+ rule.onNodeWithTag(TEST_TAG).assertExists()
+ }
+
+ @Test
+ fun openOnPhone_supports_swipeToDismiss() {
+ rule.mainClock.autoAdvance = false
+ rule.setContentWithTheme {
+ var showDialog by remember { mutableStateOf(true) }
+ OpenOnPhoneDialog(
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = { showDialog = false },
+ show = showDialog
+ )
+ }
+ rule.mainClock.advanceTimeBy(OpenOnPhoneDialogDefaults.DurationMillis / 2)
+ rule.onNodeWithTag(TEST_TAG).performTouchInput({ swipeRight() })
+ // Advancing time so that the dialog is dismissed
+ rule.mainClock.advanceTimeBy(300)
+ rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun hides_openOnPhone_when_show_false() {
+ rule.setContentWithTheme {
+ OpenOnPhoneDialog(
+ show = false,
+ modifier = Modifier.testTag(TEST_TAG),
+ onDismissRequest = {},
+ )
+ }
+ rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun openOnPhone_displays_icon() {
+ rule.setContentWithTheme {
+ OpenOnPhoneDialog(onDismissRequest = {}, show = true) { TestImage(IconTestTag) }
+ }
+ rule.onNodeWithTag(IconTestTag).assertExists()
+ }
+
+ @Test
+ fun openOnPhone_dismissed_after_timeout() {
+ var dismissed = false
+ rule.mainClock.autoAdvance = false
+ rule.setContentWithTheme {
+ OpenOnPhoneDialog(onDismissRequest = { dismissed = true }, show = true) {}
+ }
+ // Timeout longer than default confirmation duration
+ rule.mainClock.advanceTimeBy(OpenOnPhoneDialogDefaults.DurationMillis + 1000)
+ assert(dismissed)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun openOnPhone_correct_colors() {
+ rule.mainClock.autoAdvance = false
+ var expectedIconColor: Color = Color.Unspecified
+ var expectedIconContainerColor: Color = Color.Unspecified
+ var expectedProgressIndicatorColor: Color = Color.Unspecified
+ var expectedProgressTrackColor: Color = Color.Unspecified
+ rule.setContentWithTheme {
+ OpenOnPhoneDialog(
+ onDismissRequest = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ show = true
+ )
+ expectedIconColor = MaterialTheme.colorScheme.primary
+ expectedIconContainerColor = MaterialTheme.colorScheme.primaryContainer
+ expectedProgressIndicatorColor = MaterialTheme.colorScheme.primary
+ expectedProgressTrackColor = MaterialTheme.colorScheme.onPrimary
+ }
+ // Advance time by half of the default confirmation duration, so that the track and
+ // indicator are shown
+ rule.mainClock.advanceTimeBy(OpenOnPhoneDialogDefaults.DurationMillis / 2)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedIconColor)
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertContainsColor(expectedIconContainerColor)
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertContainsColor(expectedProgressIndicatorColor)
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertContainsColor(expectedProgressTrackColor)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun openOnPhone_custom_colors() {
+ rule.mainClock.autoAdvance = false
+ val customIconColor: Color = Color.Red
+ val customIconContainerColor: Color = Color.Green
+ val customProgressIndicatorColor: Color = Color.Blue
+ val customProgressTrackColor: Color = Color.Magenta
+ rule.setContentWithTheme {
+ OpenOnPhoneDialog(
+ onDismissRequest = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ colors =
+ OpenOnPhoneDialogDefaults.colors(
+ iconColor = customIconColor,
+ iconContainerColor = customIconContainerColor,
+ progressIndicatorColor = customProgressIndicatorColor,
+ progressTrackColor = customProgressTrackColor
+ ),
+ show = true
+ )
+ }
+ // Advance time by half of the default confirmation duration, so that the track and
+ // indicator are shown
+ rule.mainClock.advanceTimeBy(OpenOnPhoneDialogDefaults.DurationMillis / 2)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customIconColor)
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customIconContainerColor)
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertContainsColor(customProgressIndicatorColor)
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customProgressTrackColor)
+ }
+}
+
+private const val IconTestTag = "icon"
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
index 3b9584c..166a54c 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
@@ -611,7 +611,7 @@
private fun ComposeContentTestRule.verifyTextToggleButtonColors(
status: Status,
checked: Boolean,
- colors: @Composable () -> ToggleButtonColors,
+ colors: @Composable () -> TextToggleButtonColors,
containerColor: @Composable () -> Color,
contentColor: @Composable () -> Color,
) {
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt
index 352df46..5c81306 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt
@@ -17,6 +17,7 @@
package androidx.wear.compose.material3
import android.os.Build
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
@@ -61,7 +62,7 @@
@Test
fun time_text_with_clock_only_on_round_device() = verifyScreenshot {
TimeText(
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
time()
@@ -72,7 +73,7 @@
fun time_text_with_clock_only_on_non_round_device() =
verifyScreenshot(false) {
TimeText(
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
time()
@@ -82,7 +83,7 @@
@Test
fun time_text_with_status_on_round_device() = verifyScreenshot {
TimeText(
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
text("ETA 12:48")
@@ -95,7 +96,7 @@
fun time_text_with_status_on_non_round_device() =
verifyScreenshot(false) {
TimeText(
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
text("ETA 12:48")
@@ -107,7 +108,7 @@
@Test
fun time_text_with_icon_on_round_device() = verifyScreenshot {
TimeText(
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
time()
@@ -126,7 +127,7 @@
fun time_text_with_icon_on_non_round_device() =
verifyScreenshot(false) {
TimeText(
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
time()
@@ -149,7 +150,7 @@
TimeText(
contentColor = Color.Green,
timeTextStyle = timeTextStyle,
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
text("ETA", customStyle)
@@ -166,7 +167,7 @@
TimeText(
contentColor = Color.Green,
timeTextStyle = timeTextStyle,
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
text("Long status that should be ellipsized.")
@@ -184,7 +185,7 @@
TimeText(
contentColor = Color.Green,
timeTextStyle = timeTextStyle,
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
text("ETA", customStyle)
@@ -205,7 +206,7 @@
contentColor = Color.Green,
timeTextStyle = timeTextStyle,
maxSweepAngle = 180f,
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
text(
@@ -226,7 +227,7 @@
TimeText(
contentColor = Color.Green,
timeTextStyle = timeTextStyle,
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
text(
@@ -249,7 +250,7 @@
contentColor = Color.Green,
timeTextStyle = timeTextStyle,
maxSweepAngle = 90f,
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
) {
text(
@@ -292,7 +293,7 @@
TimeText(
contentColor = Color.Green,
maxSweepAngle = 180f,
- modifier = Modifier.testTag(TEST_TAG),
+ modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
timeSource = MockTimeSource,
content = content
)
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/dialog/AlertDialog.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
similarity index 92%
rename from wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/dialog/AlertDialog.kt
rename to wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
index 2b34a71..f0cc000 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/dialog/AlertDialog.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.wear.compose.material3.dialog
+package androidx.wear.compose.material3
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -46,25 +46,12 @@
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
-import androidx.wear.compose.material3.Button
-import androidx.wear.compose.material3.ButtonDefaults
-import androidx.wear.compose.material3.EdgeButton
-import androidx.wear.compose.material3.FilledIconButton
-import androidx.wear.compose.material3.FilledTonalIconButton
-import androidx.wear.compose.material3.Icon
-import androidx.wear.compose.material3.LocalContentColor
-import androidx.wear.compose.material3.LocalTextAlign
-import androidx.wear.compose.material3.LocalTextMaxLines
-import androidx.wear.compose.material3.LocalTextStyle
-import androidx.wear.compose.material3.MaterialTheme
-import androidx.wear.compose.material3.PaddingDefaults
-import androidx.wear.compose.material3.ScreenScaffold
-import androidx.wear.compose.material3.dialog.AlertDialogDefaults.bottomSpacing
-import androidx.wear.compose.material3.dialog.AlertDialogDefaults.contentTopSpacing
-import androidx.wear.compose.material3.dialog.AlertDialogDefaults.iconBottomSpacing
-import androidx.wear.compose.material3.dialog.AlertDialogDefaults.textMessageTopSpacing
-import androidx.wear.compose.material3.dialog.AlertDialogDefaults.textPaddingFraction
-import androidx.wear.compose.material3.dialog.AlertDialogDefaults.titlePaddingFraction
+import androidx.wear.compose.material3.AlertDialogDefaults.bottomSpacing
+import androidx.wear.compose.material3.AlertDialogDefaults.contentTopSpacing
+import androidx.wear.compose.material3.AlertDialogDefaults.iconBottomSpacing
+import androidx.wear.compose.material3.AlertDialogDefaults.textMessageTopSpacing
+import androidx.wear.compose.material3.AlertDialogDefaults.textPaddingFraction
+import androidx.wear.compose.material3.AlertDialogDefaults.titlePaddingFraction
import androidx.wear.compose.materialcore.isSmallScreen
import androidx.wear.compose.materialcore.screenWidthDp
@@ -79,7 +66,7 @@
*
* Example of an [AlertDialog] with an icon, title and two buttons to confirm and dismiss:
*
- * @sample androidx.wear.compose.material3.samples.dialog.AlertDialogWithConfirmAndDismissSample
+ * @sample androidx.wear.compose.material3.samples.AlertDialogWithConfirmAndDismissSample
* @param show A boolean indicating whether the dialog should be displayed.
* @param onDismissRequest A lambda function to be called when the dialog is dismissed by swiping
* right (typically also called by the [dismissButton]).
@@ -146,11 +133,11 @@
*
* Example of an [AlertDialog] with an icon, title, text and bottom [EdgeButton]:
*
- * @sample androidx.wear.compose.material3.samples.dialog.AlertDialogWithBottomButtonSample
+ * @sample androidx.wear.compose.material3.samples.AlertDialogWithBottomButtonSample
*
* Example of an [AlertDialog] with content groups and a bottom [EdgeButton]:
*
- * @sample androidx.wear.compose.material3.samples.dialog.AlertDialogWithContentGroupsSample
+ * @sample androidx.wear.compose.material3.samples.AlertDialogWithContentGroupsSample
* @param show A boolean indicating whether the dialog should be displayed.
* @param onDismissRequest A lambda function to be called when the dialog is dismissed by swiping to
* the right or by other dismiss action.
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
index a0f7d255..ec6ee95a 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
@@ -220,7 +220,7 @@
internal var defaultOutlinedIconButtonColorsCached: IconButtonColors? = null
// Icon Toggle Button
- internal var defaultIconToggleButtonColorsCached: ToggleButtonColors? = null
+ internal var defaultIconToggleButtonColorsCached: IconToggleButtonColors? = null
// Text Button
internal var defaultTextButtonColorsCached: TextButtonColors? = null
@@ -230,7 +230,7 @@
internal var defaultOutlinedTextButtonColorsCached: TextButtonColors? = null
// Text Toggle Button
- internal var defaultTextToggleButtonColorsCached: ToggleButtonColors? = null
+ internal var defaultTextToggleButtonColorsCached: TextToggleButtonColors? = null
// Card
internal var defaultCardColorsCached: CardColors? = null
@@ -254,8 +254,17 @@
// Level Indicator
internal var defaultLevelIndicatorColorsCached: LevelIndicatorColors? = null
+ // Confirmation
+ internal var defaultConfirmationColorsCached: ConfirmationColors? = null
+ internal var defaultSuccessConfirmationColorsCached: ConfirmationColors? = null
+ internal var defaultFailureConfirmationColorsCached: ConfirmationColors? = null
+
+ // Open on Phone dialog
+ internal var mDefaultOpenOnPhoneDialogColorsCached: OpenOnPhoneDialogColors? = null
+
// Picker
internal var defaultTimePickerColorsCached: TimePickerColors? = null
+ internal var defaultDatePickerColorsCached: DatePickerColors? = null
}
/**
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Confirmation.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Confirmation.kt
new file mode 100644
index 0000000..e88a30b
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Confirmation.kt
@@ -0,0 +1,656 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+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.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalAccessibilityManager
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.DialogProperties
+import androidx.wear.compose.foundation.CurvedDirection
+import androidx.wear.compose.foundation.CurvedLayout
+import androidx.wear.compose.foundation.CurvedModifier
+import androidx.wear.compose.foundation.CurvedScope
+import androidx.wear.compose.foundation.CurvedTextStyle
+import androidx.wear.compose.foundation.padding
+import androidx.wear.compose.material3.tokens.ColorSchemeKeyTokens
+import androidx.wear.compose.materialcore.screenHeightDp
+import androidx.wear.compose.materialcore.screenWidthDp
+import kotlinx.coroutines.delay
+
+/**
+ * Shows a [Confirmation] dialog with an icon and optional very short curved text. The length of the
+ * curved text should be very short and should not exceed 1-2 words. If a longer text required, then
+ * another [Confirmation] overload with a column content should be used instead.
+ *
+ * The confirmation will be showing a message to the user for [durationMillis]. After a specified
+ * timeout, the [onDismissRequest] callback will be invoked, where it's up to the caller to handle
+ * the dismissal. To hide the confirmation, [show] parameter should be set to false.
+ *
+ * Example of a [Confirmation] with an icon and a curved text content:
+ *
+ * @sample androidx.wear.compose.material3.samples.ConfirmationSample
+ * @param show A boolean indicating whether the confirmation should be displayed.
+ * @param onDismissRequest A lambda function to be called when the dialog is dismissed - either by
+ * swiping right or when the [durationMillis] has passed.
+ * @param curvedText A slot for displaying curved text content which will be shown along the bottom
+ * edge of the dialog.
+ * @param modifier Modifier to be applied to the confirmation content.
+ * @param colors A [ConfirmationColors] object for customizing the colors used in this
+ * [Confirmation].
+ * @param properties An optional [DialogProperties] object for configuring the dialog's behavior.
+ * @param durationMillis The duration in milliseconds for which the dialog is displayed. Defaults to
+ * [ConfirmationDefaults.ConfirmationDurationMillis].
+ * @param content A slot for displaying an icon inside the confirmation dialog. It's recommended to
+ * set its size to [ConfirmationDefaults.IconSize]
+ */
+@Composable
+fun Confirmation(
+ show: Boolean,
+ onDismissRequest: () -> Unit,
+ curvedText: (CurvedScope.() -> Unit)?,
+ modifier: Modifier = Modifier,
+ colors: ConfirmationColors = ConfirmationDefaults.confirmationColors(),
+ properties: DialogProperties = DialogProperties(),
+ durationMillis: Long = ConfirmationDefaults.ConfirmationDurationMillis,
+ content: @Composable BoxScope.() -> Unit
+) {
+ ConfirmationImpl(
+ show = show,
+ onDismissRequest = onDismissRequest,
+ modifier = modifier,
+ iconContainer = confirmationIconContainer(true, colors.iconContainerColor),
+ curvedText = curvedText,
+ colors = colors,
+ properties = properties,
+ durationMillis = durationMillis,
+ content = content
+ )
+}
+
+/**
+ * Shows a [Confirmation] dialog with an icon and optional short text. The length of the text should
+ * not exceed 3 lines. If the text is very short and fits into 1-2 words, consider using another
+ * [Confirmation] overload with curvedContent instead.
+ *
+ * The confirmation will show a message to the user for [durationMillis]. After a specified timeout,
+ * the [onDismissRequest] callback will be invoked, where it's up to the caller to handle the
+ * dismissal. To hide the confirmation, [show] parameter should be set to false.
+ *
+ * Example of a [Confirmation] with an icon and a text which fits into 3 lines:
+ *
+ * @sample androidx.wear.compose.material3.samples.LongTextConfirmationSample
+ * @param show A boolean indicating whether the confirmation should be displayed.
+ * @param onDismissRequest A lambda function to be called when the dialog is dismissed - either by
+ * swiping right or when the [durationMillis] has passed.
+ * @param text A slot for displaying text below the icon. It should not exceed 3 lines.
+ * @param modifier Modifier to be applied to the confirmation content.
+ * @param colors A [ConfirmationColors] object for customizing the colors used in this
+ * [Confirmation].
+ * @param properties An optional [DialogProperties] object for configuring the dialog's behavior.
+ * @param durationMillis The duration in milliseconds for which the dialog is displayed. Defaults to
+ * [ConfirmationDefaults.ConfirmationDurationMillis].
+ * @param content A slot for displaying an icon inside the confirmation dialog, which can be
+ * animated. It's recommended to set its size to [ConfirmationDefaults.SmallIconSize]
+ */
+@Composable
+fun Confirmation(
+ show: Boolean,
+ onDismissRequest: () -> Unit,
+ text: @Composable (ColumnScope.() -> Unit)?,
+ modifier: Modifier = Modifier,
+ colors: ConfirmationColors = ConfirmationDefaults.confirmationColors(),
+ properties: DialogProperties = DialogProperties(),
+ durationMillis: Long = ConfirmationDefaults.ConfirmationDurationMillis,
+ content: @Composable BoxScope.() -> Unit
+) {
+
+ val a11yDurationMillis =
+ LocalAccessibilityManager.current?.calculateRecommendedTimeoutMillis(
+ originalTimeoutMillis = durationMillis,
+ containsIcons = true,
+ containsText = text != null,
+ containsControls = false,
+ ) ?: durationMillis
+
+ LaunchedEffect(show, a11yDurationMillis) {
+ if (show) {
+ delay(a11yDurationMillis)
+ onDismissRequest()
+ }
+ }
+
+ Dialog(
+ showDialog = show,
+ modifier = modifier,
+ onDismissRequest = onDismissRequest,
+ properties = properties,
+ ) {
+ Box(Modifier.fillMaxSize()) {
+ val horizontalPadding =
+ screenWidthDp().dp * ConfirmationDefaults.HorizontalLinearContentPaddingFraction
+ Column(
+ modifier = Modifier.align(Alignment.Center).padding(horizontal = horizontalPadding),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Box(
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ contentAlignment = Alignment.Center
+ ) {
+ confirmationIconContainer(false, colors.iconContainerColor)()
+ CompositionLocalProvider(LocalContentColor provides colors.iconColor) {
+ content()
+ }
+ }
+ CompositionLocalProvider(
+ LocalContentColor provides colors.textColor,
+ LocalTextStyle provides MaterialTheme.typography.titleMedium,
+ LocalTextAlign provides TextAlign.Center,
+ LocalTextMaxLines provides ConfirmationDefaults.LinearContentMaxLines
+ ) {
+ if (text != null) {
+ Spacer(Modifier.height(ConfirmationDefaults.LinearContentSpacing))
+ text()
+ Spacer(Modifier.height(ConfirmationDefaults.LinearContentSpacing))
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Shows a [Confirmation] dialog with a success icon and optional short curved text. This
+ * confirmation indicates a successful operation or action.
+ *
+ * The confirmation will show a message to the user for [durationMillis]. After a specified timeout,
+ * the [onDismissRequest] callback will be invoked, where it's up to the caller to handle the
+ * dismissal. To hide the confirmation, [show] parameter should be set to false.
+ *
+ * Example of a [SuccessConfirmation] usage:
+ *
+ * @sample androidx.wear.compose.material3.samples.SuccessConfirmationSample
+ * @param show A boolean indicating whether the confirmation should be displayed.
+ * @param onDismissRequest A lambda function to be called when the dialog is dismissed - either by
+ * swiping right or when the [durationMillis] has passed.
+ * @param modifier Modifier to be applied to the confirmation content.
+ * @param curvedText A slot for displaying curved text content which will be shown along the bottom
+ * edge of the dialog. Defaults to a localized success message.
+ * @param colors A [ConfirmationColors] object for customizing the colors used in this
+ * [SuccessConfirmation].
+ * @param properties An optional [DialogProperties] object for configuring the dialog's behavior.
+ * @param durationMillis The duration in milliseconds for which the dialog is displayed. Defaults to
+ * [ConfirmationDefaults.ConfirmationDurationMillis].
+ * @param content A slot for displaying an icon inside the confirmation dialog, which can be
+ * animated. Defaults to an animated [ConfirmationDefaults.SuccessIcon].
+ */
+@Composable
+fun SuccessConfirmation(
+ show: Boolean,
+ onDismissRequest: () -> Unit,
+ modifier: Modifier = Modifier,
+ curvedText: (CurvedScope.() -> Unit)? = ConfirmationDefaults.successText(),
+ colors: ConfirmationColors = ConfirmationDefaults.successColors(),
+ properties: DialogProperties = DialogProperties(),
+ durationMillis: Long = ConfirmationDefaults.ConfirmationDurationMillis,
+ content: @Composable BoxScope.() -> Unit = ConfirmationDefaults.SuccessIcon,
+) {
+ ConfirmationImpl(
+ show = show,
+ onDismissRequest = onDismissRequest,
+ modifier = modifier,
+ content = content,
+ iconContainer = successIconContainer(colors.iconContainerColor),
+ curvedText = curvedText,
+ colors = colors,
+ properties = properties,
+ durationMillis = durationMillis
+ )
+}
+
+/**
+ * Shows a [Confirmation] dialog with a failure icon and an optional short curved text. This
+ * confirmation indicates an unsuccessful operation or action.
+ *
+ * The confirmation will show a message to the user for [durationMillis]. After a specified timeout,
+ * the [onDismissRequest] callback will be invoked, where it's up to the caller to handle the
+ * dismissal. To hide the confirmation, [show] parameter should be set to false.
+ *
+ * Example of a [FailureConfirmation] usage:
+ *
+ * @sample androidx.wear.compose.material3.samples.FailureConfirmationSample
+ * @param show A boolean indicating whether the confirmation should be displayed.
+ * @param onDismissRequest A lambda function to be called when the dialog is dismissed - either by
+ * swiping right or when the [durationMillis] has passed.
+ * @param modifier Modifier to be applied to the confirmation content.
+ * @param curvedText A slot for displaying curved text content which will be shown along the bottom
+ * edge of the dialog. Defaults to a localized failure message.
+ * @param colors A [ConfirmationColors] object for customizing the colors used in this
+ * [FailureConfirmation].
+ * @param properties An optional [DialogProperties] object for configuring the dialog's behavior.
+ * @param durationMillis The duration in milliseconds for which the dialog is displayed. Defaults to
+ * [ConfirmationDefaults.ConfirmationDurationMillis].
+ * @param content A slot for displaying an icon inside the confirmation dialog, which can be
+ * animated. Defaults to [ConfirmationDefaults.FailureIcon].
+ */
+@Composable
+fun FailureConfirmation(
+ show: Boolean,
+ onDismissRequest: () -> Unit,
+ modifier: Modifier = Modifier,
+ curvedText: (CurvedScope.() -> Unit)? = ConfirmationDefaults.failureText(),
+ colors: ConfirmationColors = ConfirmationDefaults.failureColors(),
+ properties: DialogProperties = DialogProperties(),
+ durationMillis: Long = ConfirmationDefaults.ConfirmationDurationMillis,
+ content: @Composable BoxScope.() -> Unit = ConfirmationDefaults.FailureIcon,
+) {
+ ConfirmationImpl(
+ show = show,
+ onDismissRequest = onDismissRequest,
+ modifier = modifier,
+ iconContainer = failureIconContainer(colors.iconContainerColor),
+ curvedText = curvedText,
+ colors = colors,
+ properties = properties,
+ durationMillis = durationMillis,
+ content = content
+ )
+}
+
+/** Contains default values used by [Confirmation] composable. */
+object ConfirmationDefaults {
+
+ /**
+ * Returns a lambda to display a curved success message. The success message is retrieved from
+ * the application's string resources.
+ */
+ @Composable
+ fun successText(): CurvedScope.() -> Unit =
+ curvedText(
+ LocalContext.current.resources.getString(R.string.wear_m3c_confirmation_success_message)
+ )
+
+ /**
+ * Returns a lambda to display a curved failure message. The failure message is retrieved from
+ * the application's string resources.
+ */
+ @Composable
+ fun failureText(): CurvedScope.() -> Unit =
+ curvedText(
+ LocalContext.current.resources.getString(R.string.wear_m3c_confirmation_failure_message)
+ )
+
+ /**
+ * A default composable used in [SuccessConfirmation] that displays a success icon with an
+ * animation.
+ */
+ @OptIn(ExperimentalAnimationGraphicsApi::class)
+ val SuccessIcon: @Composable BoxScope.() -> Unit = {
+ val animation = AnimatedImageVector.animatedVectorResource(R.drawable.check_animation)
+ var atEnd by remember { mutableStateOf(false) }
+ LaunchedEffect(Unit) {
+ delay(FailureIconDelay)
+ atEnd = true
+ }
+ Icon(
+ painter = rememberAnimatedVectorPainter(animation, atEnd),
+ contentDescription = null,
+ modifier = Modifier.size(IconSize)
+ )
+ }
+
+ /**
+ * A default composable used in [FailureConfirmation] that displays a failure icon with an
+ * animation.
+ */
+ @OptIn(ExperimentalAnimationGraphicsApi::class)
+ val FailureIcon: @Composable BoxScope.() -> Unit = {
+ val animation = AnimatedImageVector.animatedVectorResource(R.drawable.failure_animation)
+ var atEnd by remember { mutableStateOf(false) }
+ LaunchedEffect(Unit) {
+ delay(FailureIconDelay)
+ atEnd = true
+ }
+ Icon(
+ painter = rememberAnimatedVectorPainter(animation, atEnd),
+ contentDescription = null,
+ modifier = Modifier.size(IconSize)
+ )
+ }
+
+ /**
+ * A default composable that displays text along a curved path, used in [Confirmation].
+ *
+ * @param text The text to display.
+ * @param style The style to apply to the text. Defaults to
+ * CurvedTextStyle(MaterialTheme.typography.titleLarge).
+ */
+ @Composable
+ fun curvedText(
+ text: String,
+ style: CurvedTextStyle = CurvedTextStyle(MaterialTheme.typography.titleLarge)
+ ): CurvedScope.() -> Unit = {
+ curvedText(
+ text = text,
+ style = style,
+ maxSweepAngle = CurvedTextDefaults.StaticContentMaxSweepAngle,
+ modifier = CurvedModifier.padding(PaddingDefaults.edgePadding),
+ angularDirection = CurvedDirection.Angular.Reversed
+ )
+ }
+
+ /**
+ * Creates a [ConfirmationColors] that represents the default colors used in a [Confirmation].
+ */
+ @Composable fun confirmationColors() = MaterialTheme.colorScheme.defaultConfirmationColors
+
+ /**
+ * Creates a [ConfirmationColors] with modified colors used in [Confirmation].
+ *
+ * @param iconColor The icon color.
+ * @param iconContainerColor The icon container color.
+ * @param textColor The text color.
+ */
+ @Composable
+ fun confirmationColors(
+ iconColor: Color = Color.Unspecified,
+ iconContainerColor: Color = Color.Unspecified,
+ textColor: Color = Color.Unspecified,
+ ) =
+ MaterialTheme.colorScheme.defaultConfirmationColors.copy(
+ iconColor = iconColor,
+ iconContainerColor = iconContainerColor,
+ textColor = textColor,
+ )
+
+ /**
+ * Creates a [ConfirmationColors] that represents the default colors used in a
+ * [SuccessConfirmation].
+ */
+ @Composable fun successColors() = MaterialTheme.colorScheme.defaultSuccessConfirmationColors
+
+ /**
+ * Creates a [ConfirmationColors] with modified colors used in [SuccessConfirmation].
+ *
+ * @param iconColor The icon color.
+ * @param iconContainerColor The icon container color.
+ * @param textColor The text color.
+ */
+ @Composable
+ fun successColors(
+ iconColor: Color = Color.Unspecified,
+ iconContainerColor: Color = Color.Unspecified,
+ textColor: Color = Color.Unspecified,
+ ) =
+ MaterialTheme.colorScheme.defaultSuccessConfirmationColors.copy(
+ iconColor = iconColor,
+ iconContainerColor = iconContainerColor,
+ textColor = textColor,
+ )
+
+ /**
+ * Creates a [ConfirmationColors] that represents the default colors used in a
+ * [FailureConfirmation].
+ */
+ @Composable fun failureColors() = MaterialTheme.colorScheme.defaultFailureConfirmationColors
+
+ /**
+ * Creates a [ConfirmationColors] with modified colors used in [FailureConfirmation].
+ *
+ * @param iconColor The icon color.
+ * @param iconContainerColor The icon container color.
+ * @param textColor The text color.
+ */
+ @Composable
+ fun failureColors(
+ iconColor: Color = Color.Unspecified,
+ iconContainerColor: Color = Color.Unspecified,
+ textColor: Color = Color.Unspecified,
+ ) =
+ MaterialTheme.colorScheme.defaultFailureConfirmationColors.copy(
+ iconColor = iconColor,
+ iconContainerColor = iconContainerColor,
+ textColor = textColor,
+ )
+
+ /** Default timeout for the [Confirmation] dialog, in milliseconds. */
+ const val ConfirmationDurationMillis = 4000L
+
+ /** Default icon size for the [Confirmation] with curved content */
+ val IconSize = 52.dp
+
+ /** Default icon size for the [Confirmation] with linear content */
+ val SmallIconSize = 36.dp
+
+ private val ColorScheme.defaultConfirmationColors: ConfirmationColors
+ get() {
+ return defaultConfirmationColorsCached
+ ?: ConfirmationColors(
+ iconColor = fromToken(ColorSchemeKeyTokens.Primary),
+ iconContainerColor = fromToken(ColorSchemeKeyTokens.OnPrimary),
+ textColor = fromToken(ColorSchemeKeyTokens.OnBackground)
+ )
+ .also { defaultConfirmationColorsCached = it }
+ }
+
+ private val ColorScheme.defaultSuccessConfirmationColors: ConfirmationColors
+ get() {
+ return defaultSuccessConfirmationColorsCached
+ ?: ConfirmationColors(
+ iconColor = fromToken(ColorSchemeKeyTokens.Primary),
+ iconContainerColor = fromToken(ColorSchemeKeyTokens.OnPrimary),
+ textColor = fromToken(ColorSchemeKeyTokens.OnBackground)
+ )
+ .also { defaultSuccessConfirmationColorsCached = it }
+ }
+
+ private val ColorScheme.defaultFailureConfirmationColors: ConfirmationColors
+ get() {
+ return defaultFailureConfirmationColorsCached
+ ?: ConfirmationColors(
+ iconColor = fromToken(ColorSchemeKeyTokens.ErrorContainer),
+ iconContainerColor =
+ fromToken(ColorSchemeKeyTokens.OnErrorContainer).copy(.8f),
+ textColor = fromToken(ColorSchemeKeyTokens.OnBackground)
+ )
+ .also { defaultFailureConfirmationColorsCached = it }
+ }
+
+ internal val FailureIconDelay = 67L
+
+ internal val SuccessWidthFraction = 0.496f
+ internal val SuccessHeightFraction = 0.6f
+ internal val FailureSizeFraction = 0.52f
+
+ internal val ConfirmationIconContainerSmallSize = 80.dp
+ internal val ConfirmationIconContainerSizeFraction = 0.52
+
+ internal val ExtraBottomPaddingFraction = 0.02f
+
+ internal val LinearContentSpacing = 8.dp
+ internal val LinearContentMaxLines = 3
+ internal val HorizontalLinearContentPaddingFraction = 0.12f
+}
+
+/**
+ * Represents the colors used in [Confirmation], [SuccessConfirmation] and [FailureConfirmation].
+ *
+ * @param iconColor Color used to tint the icon.
+ * @param iconContainerColor The color of the container behind the icon.
+ * @param textColor Color used to tint the text.
+ */
+class ConfirmationColors(
+ val iconColor: Color,
+ val iconContainerColor: Color,
+ val textColor: Color,
+) {
+ internal fun copy(
+ iconColor: Color? = null,
+ iconContainerColor: Color? = null,
+ textColor: Color? = null
+ ) =
+ ConfirmationColors(
+ iconColor = iconColor ?: this.iconColor,
+ iconContainerColor = iconContainerColor ?: this.iconContainerColor,
+ textColor = textColor ?: this.textColor,
+ )
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || other !is ConfirmationColors) return false
+
+ if (iconColor != other.iconColor) return false
+ if (iconContainerColor != other.iconContainerColor) return false
+ if (textColor != other.textColor) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = iconColor.hashCode()
+ result = 31 * result + iconContainerColor.hashCode()
+ result = 31 * result + textColor.hashCode()
+ return result
+ }
+}
+
+@Composable
+internal fun ConfirmationImpl(
+ show: Boolean,
+ onDismissRequest: () -> Unit,
+ modifier: Modifier,
+ iconContainer: @Composable BoxScope.() -> Unit,
+ curvedText: (CurvedScope.() -> Unit)?,
+ colors: ConfirmationColors,
+ properties: DialogProperties,
+ durationMillis: Long,
+ content: @Composable BoxScope.() -> Unit
+) {
+ val a11yDurationMillis =
+ LocalAccessibilityManager.current?.calculateRecommendedTimeoutMillis(
+ originalTimeoutMillis = durationMillis,
+ containsIcons = true,
+ containsText = curvedText != null,
+ containsControls = false,
+ ) ?: durationMillis
+
+ LaunchedEffect(show, a11yDurationMillis) {
+ if (show) {
+ delay(a11yDurationMillis)
+ onDismissRequest()
+ }
+ }
+
+ Dialog(
+ showDialog = show,
+ modifier = modifier,
+ onDismissRequest = onDismissRequest,
+ properties = properties,
+ ) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ val bottomPadding =
+ if (curvedText != null)
+ screenHeightDp() * ConfirmationDefaults.ExtraBottomPaddingFraction
+ else 0f
+ Box(
+ Modifier.fillMaxSize().padding(bottom = bottomPadding.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ iconContainer()
+ CompositionLocalProvider(LocalContentColor provides colors.iconColor) { content() }
+ }
+ CompositionLocalProvider(LocalContentColor provides colors.textColor) {
+ curvedText?.let { CurvedLayout(anchor = 90f, contentBuilder = curvedText) }
+ }
+ }
+ }
+}
+
+private fun confirmationIconContainer(
+ curvedContent: Boolean,
+ color: Color
+): @Composable BoxScope.() -> Unit = {
+ val iconShape =
+ if (curvedContent) MaterialTheme.shapes.extraLarge else MaterialTheme.shapes.large
+ val width =
+ if (curvedContent) {
+ (screenWidthDp() * ConfirmationDefaults.ConfirmationIconContainerSizeFraction).dp
+ } else ConfirmationDefaults.ConfirmationIconContainerSmallSize
+
+ Box(
+ Modifier.size(width)
+ .graphicsLayer {
+ shape = iconShape
+ clip = true
+ }
+ .background(color)
+ .align(Alignment.Center)
+ )
+}
+
+private fun successIconContainer(color: Color): @Composable BoxScope.() -> Unit = {
+ val width = screenWidthDp() * ConfirmationDefaults.SuccessWidthFraction
+ val height = screenWidthDp() * ConfirmationDefaults.SuccessHeightFraction
+ Box(
+ Modifier.size(width.dp, height.dp)
+ .graphicsLayer {
+ rotationZ = 45f
+ shape = CircleShape
+ clip = true
+ }
+ .background(color)
+ )
+}
+
+private fun failureIconContainer(color: Color): @Composable BoxScope.() -> Unit = {
+ val iconShape = MaterialTheme.shapes.extraLarge
+ val width = screenWidthDp() * ConfirmationDefaults.FailureSizeFraction
+ Box(
+ Modifier.size(width.dp)
+ .graphicsLayer {
+ shape = iconShape
+ clip = true
+ }
+ .background(color)
+ )
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CurvedText.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CurvedText.kt
index 640c443..265ad90 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CurvedText.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CurvedText.kt
@@ -16,6 +16,7 @@
package androidx.wear.compose.material3
+import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.text.TextStyle
@@ -141,4 +142,10 @@
* scrollable content.
*/
const val StaticContentMaxSweepAngle: Float = 120f
+
+ /**
+ * The recommended background color to use when displaying curved text so it is visible on top
+ * of other content.
+ */
+ @Composable fun backgroundColor() = MaterialTheme.colorScheme.background.copy(alpha = 0.85f)
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt
new file mode 100644
index 0000000..8283267
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt
@@ -0,0 +1,823 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import android.os.Build
+import android.text.format.DateFormat
+import androidx.annotation.RequiresApi
+import androidx.compose.animation.core.Animatable
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+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.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableIntState
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.semantics.focused
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.rememberTextMeasurer
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.ButtonDefaults.buttonColors
+import androidx.wear.compose.material3.ButtonDefaults.filledTonalButtonColors
+import androidx.wear.compose.material3.internal.Strings.Companion.DatePickerDay
+import androidx.wear.compose.material3.internal.Strings.Companion.DatePickerMonth
+import androidx.wear.compose.material3.internal.Strings.Companion.DatePickerYear
+import androidx.wear.compose.material3.internal.Strings.Companion.PickerConfirmButtonContentDescription
+import androidx.wear.compose.material3.internal.Strings.Companion.PickerNextButtonContentDescription
+import androidx.wear.compose.material3.internal.getString
+import androidx.wear.compose.material3.tokens.DatePickerTokens
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+import java.time.temporal.TemporalAdjusters
+
+/**
+ * Full screen date picker with day, month, year.
+ *
+ * This component is designed to take most/all of the screen and utilizes large fonts.
+ *
+ * Example of a [DatePicker]:
+ *
+ * @sample androidx.wear.compose.material3.samples.DatePickerSample
+ *
+ * Example of a [DatePicker] shows the picker options in year-month-day order:
+ *
+ * @sample androidx.wear.compose.material3.samples.DatePickerYearMonthDaySample
+ *
+ * Example of a [DatePicker] with fromDate and toDate:
+ *
+ * @sample androidx.wear.compose.material3.samples.DatePickerFromDateToDateSample
+ * @param initialDate The initial value to be displayed in the DatePicker.
+ * @param onDatePicked The callback that is called when the user confirms the date selection. It
+ * provides the selected date as [LocalDate]
+ * @param modifier Modifier to be applied to the `Box` containing the UI elements.
+ * @param minDate Optional minimum date that can be selected in the DatePicker (inclusive).
+ * @param maxDate Optional maximum date that can be selected in the DatePicker (inclusive).
+ * @param datePickerType The different [DatePickerType] supported by this date picker.
+ * @param colors [DatePickerColors] to be applied to the DatePicker.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+@Composable
+fun DatePicker(
+ initialDate: LocalDate,
+ onDatePicked: (LocalDate) -> Unit,
+ modifier: Modifier = Modifier,
+ minDate: LocalDate? = null,
+ maxDate: LocalDate? = null,
+ datePickerType: DatePickerType = DatePickerDefaults.datePickerType,
+ colors: DatePickerColors = DatePickerDefaults.datePickerColors()
+) {
+ val inspectionMode = LocalInspectionMode.current
+ val fullyDrawn = remember { Animatable(if (inspectionMode) 1f else 0f) }
+
+ if (minDate != null && maxDate != null) {
+ verifyDates(initialDate, minDate, maxDate)
+ }
+
+ val datePickerState = remember(initialDate) { DatePickerState(initialDate, minDate, maxDate) }
+
+ val touchExplorationStateProvider = remember { DefaultTouchExplorationStateProvider() }
+ val touchExplorationServicesEnabled by touchExplorationStateProvider.touchExplorationState()
+
+ // When the time picker loads, none of the individual pickers are selected in talkback mode,
+ // otherwise first picker should be focused.
+ val pickerGroupState =
+ if (touchExplorationServicesEnabled) {
+ rememberPickerGroupState(NoneSelectedIndex)
+ } else {
+ rememberPickerGroupState(0)
+ }
+
+ val isLargeScreen = LocalConfiguration.current.screenWidthDp > 225
+ val labelTextStyle =
+ if (isLargeScreen) {
+ DatePickerTokens.PickerLabelLargeTypography.value
+ } else {
+ DatePickerTokens.PickerLabelTypography.value
+ }
+ val optionTextStyle =
+ if (isLargeScreen) {
+ DatePickerTokens.PickerContentLargeTypography.value
+ } else {
+ DatePickerTokens.PickerContentTypography.value
+ }
+ val optionHeight = if (isLargeScreen) 48.dp else 36.dp
+
+ val focusRequesterConfirmButton = remember { FocusRequester() }
+
+ val yearString = getString(DatePickerYear)
+ val monthString = getString(DatePickerMonth)
+ val dayString = getString(DatePickerDay)
+
+ val prevStartMonth = remember { mutableIntStateOf(datePickerState.monthOptionStartMonth) }
+ LaunchedEffect(datePickerState.yearState.selectedOption) {
+ adjustOptionSelection(
+ prevStartState = prevStartMonth,
+ currentStartValue = datePickerState.monthOptionStartMonth,
+ currentNumberOfOptions = datePickerState.numberOfMonth,
+ pickerState = datePickerState.monthState,
+ )
+ }
+
+ val prevStartDay = remember { mutableIntStateOf(datePickerState.dayOptionStartDay) }
+ LaunchedEffect(
+ datePickerState.yearState.selectedOption,
+ datePickerState.monthState.selectedOption
+ ) {
+ adjustOptionSelection(
+ prevStartState = prevStartDay,
+ currentStartValue = datePickerState.dayOptionStartDay,
+ currentNumberOfOptions = datePickerState.numberOfDay,
+ pickerState = datePickerState.dayState,
+ )
+ }
+
+ val shortMonthNames = remember { getMonthNames("MMM") }
+ val fullMonthNames = remember { getMonthNames("MMMM") }
+ val yearContentDescription by
+ remember(
+ pickerGroupState.selectedIndex,
+ datePickerState.currentYear(),
+ ) {
+ derivedStateOf {
+ createDescriptionDatePicker(
+ pickerGroupState,
+ datePickerState.currentYear(),
+ yearString,
+ )
+ }
+ }
+ val monthContentDescription by
+ remember(
+ pickerGroupState.selectedIndex,
+ datePickerState.currentMonth(),
+ ) {
+ derivedStateOf {
+ if (pickerGroupState.selectedIndex == NoneSelectedIndex) {
+ monthString
+ } else {
+ fullMonthNames[(datePickerState.currentMonth() - 1) % 12]
+ }
+ }
+ }
+ val dayContentDescription by
+ remember(
+ pickerGroupState.selectedIndex,
+ datePickerState.currentDay(),
+ ) {
+ derivedStateOf {
+ createDescriptionDatePicker(
+ pickerGroupState,
+ datePickerState.currentDay(),
+ dayString,
+ )
+ }
+ }
+
+ val datePickerOptions = datePickerType.toDatePickerOptions()
+ val confirmButtonIndex = datePickerOptions.size
+
+ val onPickerSelected = { current: Int, next: Int ->
+ if (pickerGroupState.selectedIndex != current) {
+ pickerGroupState.selectedIndex = current
+ } else {
+ pickerGroupState.selectedIndex = next
+ if (next == confirmButtonIndex) {
+ focusRequesterConfirmButton.requestFocus()
+ }
+ }
+ }
+
+ BoxWithConstraints(modifier = modifier.fillMaxSize().alpha(fullyDrawn.value)) {
+ val boxConstraints = this
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(Modifier.height(14.dp))
+ Text(
+ text =
+ when (datePickerOptions.getOrNull(pickerGroupState.selectedIndex)) {
+ DatePickerOption.Day -> dayString
+ DatePickerOption.Month -> monthString
+ DatePickerOption.Year -> yearString
+ else -> ""
+ },
+ color = colors.pickerLabelColor,
+ style = labelTextStyle,
+ maxLines = 1,
+ )
+ Spacer(Modifier.height(if (isLargeScreen) 6.dp else 4.dp))
+ FontScaleIndependent {
+ val measurer = rememberTextMeasurer()
+ val density = LocalDensity.current
+ val (digitWidth, maxMonthWidth) =
+ remember(
+ density.density,
+ LocalConfiguration.current.screenWidthDp,
+ ) {
+ val mm =
+ measurer.measure(
+ "0123456789\n" + shortMonthNames.joinToString("\n"),
+ style = optionTextStyle,
+ density = density,
+ )
+
+ ((0..9).maxOf { mm.getBoundingBox(it).width }) to
+ ((1..12).maxOf { mm.getLineRight(it) - mm.getLineLeft(it) })
+ }
+
+ // Add spaces on to allow room to grow
+ val dayWidth =
+ with(LocalDensity.current) {
+ maxOf(
+ // Add 1dp buffer to compensate for potential conversion loss
+ (digitWidth * 2).toDp() + 1.dp,
+ minimumInteractiveComponentSize
+ )
+ }
+ val monthYearWidth =
+ with(LocalDensity.current) {
+ maxOf(
+ // Add 1dp buffer to compensate for potential conversion loss
+ maxOf(maxMonthWidth.toDp(), (digitWidth * 4).toDp()) + 1.dp,
+ minimumInteractiveComponentSize
+ )
+ }
+
+ Row(
+ modifier =
+ Modifier.fillMaxWidth()
+ .weight(1f)
+ .offset(
+ getPickerGroupRowOffset(
+ boxConstraints.maxWidth,
+ dayWidth,
+ monthYearWidth,
+ monthYearWidth,
+ touchExplorationServicesEnabled,
+ pickerGroupState,
+ ),
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ val spacing = if (isLargeScreen) 6.dp else 4.dp
+
+ val pickerGroupItems =
+ datePickerOptions.mapIndexed { index, datePickerOption ->
+ when (datePickerOption) {
+ DatePickerOption.Day ->
+ PickerGroupItem(
+ pickerState = datePickerState.dayState,
+ modifier = Modifier.width(dayWidth).fillMaxHeight(),
+ onSelected = { onPickerSelected(index, index + 1) },
+ contentDescription = dayContentDescription,
+ option =
+ pickerTextOption(
+ textStyle = optionTextStyle,
+ indexToText = {
+ "%02d".format(datePickerState.currentDay(it))
+ },
+ optionHeight = optionHeight,
+ selectedContentColor =
+ colors.selectedPickerContentColor,
+ unselectedContentColor =
+ colors.unselectedPickerContentColor,
+ ),
+ spacing = spacing,
+ )
+ DatePickerOption.Month ->
+ PickerGroupItem(
+ pickerState = datePickerState.monthState,
+ modifier = Modifier.width(monthYearWidth).fillMaxHeight(),
+ onSelected = { onPickerSelected(index, index + 1) },
+ contentDescription = monthContentDescription,
+ option =
+ pickerTextOption(
+ textStyle = optionTextStyle,
+ indexToText = {
+ shortMonthNames[
+ (datePickerState.currentMonth(it) - 1) % 12]
+ },
+ optionHeight = optionHeight,
+ selectedContentColor =
+ colors.selectedPickerContentColor,
+ unselectedContentColor =
+ colors.unselectedPickerContentColor,
+ ),
+ spacing = spacing,
+ )
+ DatePickerOption.Year ->
+ PickerGroupItem(
+ pickerState = datePickerState.yearState,
+ modifier = Modifier.width(monthYearWidth).fillMaxHeight(),
+ onSelected = { onPickerSelected(index, index + 1) },
+ contentDescription = yearContentDescription,
+ option =
+ pickerTextOption(
+ textStyle = optionTextStyle,
+ indexToText = {
+ "%4d".format(datePickerState.currentYear(it))
+ },
+ optionHeight = optionHeight,
+ selectedContentColor =
+ colors.selectedPickerContentColor,
+ unselectedContentColor =
+ colors.unselectedPickerContentColor,
+ ),
+ spacing = spacing,
+ )
+ }
+ }
+
+ PickerGroup(
+ *pickerGroupItems.toTypedArray(),
+ pickerGroupState = pickerGroupState,
+ autoCenter = true,
+ separator = { Spacer(Modifier.width(if (isLargeScreen) 12.dp else 8.dp)) },
+ touchExplorationStateProvider = touchExplorationStateProvider,
+ )
+ }
+ }
+ Spacer(Modifier.height(if (isLargeScreen) 6.dp else 4.dp))
+ EdgeButton(
+ onClick = {
+ if (pickerGroupState.selectedIndex >= 2) {
+ val confirmedYear: Int = datePickerState.currentYear()
+ val confirmedMonth: Int = datePickerState.currentMonth()
+ val confirmedDay: Int = datePickerState.currentDay()
+ val confirmedDate =
+ LocalDate.of(confirmedYear, confirmedMonth, confirmedDay)
+ onDatePicked(confirmedDate)
+ } else {
+ onPickerSelected(
+ pickerGroupState.selectedIndex,
+ pickerGroupState.selectedIndex + 1
+ )
+ }
+ },
+ modifier =
+ Modifier.semantics {
+ focused = pickerGroupState.selectedIndex == confirmButtonIndex
+ }
+ .focusRequester(focusRequesterConfirmButton)
+ .focusable(),
+ colors =
+ if (pickerGroupState.selectedIndex >= 2) {
+ buttonColors(
+ contentColor = colors.confirmButtonContentColor,
+ containerColor = colors.confirmButtonContainerColor,
+ )
+ } else {
+ filledTonalButtonColors(
+ contentColor = colors.nextButtonContentColor,
+ containerColor = colors.nextButtonContainerColor,
+ )
+ }
+ ) {
+ Icon(
+ imageVector =
+ if (pickerGroupState.selectedIndex < 2) {
+ Icons.AutoMirrored.Filled.KeyboardArrowRight
+ } else {
+ Icons.Filled.Check
+ },
+ contentDescription =
+ if (pickerGroupState.selectedIndex >= 2) {
+ getString(PickerConfirmButtonContentDescription)
+ } else {
+ getString(PickerNextButtonContentDescription)
+ },
+ modifier = Modifier.size(24.dp).wrapContentSize(align = Alignment.Center),
+ )
+ }
+ }
+ }
+
+ if (!inspectionMode) {
+ LaunchedEffect(Unit) { fullyDrawn.animateTo(1f) }
+ }
+}
+
+/** Specifies the types of columns to display in the DatePicker. */
+@Immutable
+@JvmInline
+value class DatePickerType internal constructor(internal val value: Int) {
+
+ companion object {
+ val DayMonthYear = DatePickerType(0)
+ val MonthDayYear = DatePickerType(1)
+ val YearMonthDay = DatePickerType(2)
+ }
+
+ override fun toString(): String {
+ return when (this) {
+ DayMonthYear -> "DayMonthYear"
+ MonthDayYear -> "MonthDayYear"
+ YearMonthDay -> "YearMonthDay"
+ else -> "Unknown"
+ }
+ }
+}
+
+/** Contains the default values used by [DatePicker] */
+object DatePickerDefaults {
+
+ /** The default [DatePickerType] for [DatePicker] aligns with the current system date format. */
+ val datePickerType: DatePickerType
+ @Composable
+ get() {
+ val formatOrder = DateFormat.getDateFormatOrder(LocalContext.current)
+ return when (formatOrder[0]) {
+ 'M' -> DatePickerType.MonthDayYear
+ 'y' -> DatePickerType.YearMonthDay
+ else -> DatePickerType.DayMonthYear
+ }
+ }
+
+ /** Creates a [DatePickerColors] for a [DatePicker]. */
+ @Composable fun datePickerColors() = MaterialTheme.colorScheme.defaultDatePickerColors
+
+ /**
+ * Creates a [DatePickerColors] for a [DatePicker].
+ *
+ * @param selectedPickerContentColor The content color of selected picker.
+ * @param unselectedPickerContentColor The content color of unselected picker.
+ * @param pickerLabelColor The color of the picker label.
+ * @param nextButtonContentColor The content color of the next button.
+ * @param nextButtonContainerColor The container color of the next button.
+ * @param confirmButtonContentColor The content color of the confirm button.
+ * @param confirmButtonContainerColor The container color of the confirm button.
+ */
+ @Composable
+ fun datePickerColors(
+ selectedPickerContentColor: Color = Color.Unspecified,
+ unselectedPickerContentColor: Color = Color.Unspecified,
+ pickerLabelColor: Color = Color.Unspecified,
+ nextButtonContentColor: Color = Color.Unspecified,
+ nextButtonContainerColor: Color = Color.Unspecified,
+ confirmButtonContentColor: Color = Color.Unspecified,
+ confirmButtonContainerColor: Color = Color.Unspecified,
+ ) =
+ MaterialTheme.colorScheme.defaultDatePickerColors.copy(
+ selectedPickerContentColor = selectedPickerContentColor,
+ unselectedPickerContentColor = unselectedPickerContentColor,
+ pickerLabelColor = pickerLabelColor,
+ nextButtonContentColor = nextButtonContentColor,
+ nextButtonContainerColor = nextButtonContainerColor,
+ confirmButtonContentColor = confirmButtonContentColor,
+ confirmButtonContainerColor = confirmButtonContainerColor,
+ )
+
+ private val ColorScheme.defaultDatePickerColors: DatePickerColors
+ get() {
+ return defaultDatePickerColorsCached
+ ?: DatePickerColors(
+ selectedPickerContentColor =
+ fromToken(DatePickerTokens.SelectedPickerContentColor),
+ unselectedPickerContentColor =
+ fromToken(DatePickerTokens.UnselectedPickerContentColor),
+ pickerLabelColor = fromToken(DatePickerTokens.PickerLabelColor),
+ nextButtonContentColor = fromToken(DatePickerTokens.NextButtonContentColor),
+ nextButtonContainerColor =
+ fromToken(DatePickerTokens.NextButtonContainerColor),
+ confirmButtonContentColor =
+ fromToken(DatePickerTokens.ConfirmButtonContentColor),
+ confirmButtonContainerColor =
+ fromToken(DatePickerTokens.ConfirmButtonContainerColor),
+ )
+ .also { defaultDatePickerColorsCached = it }
+ }
+}
+
+@Immutable
+class DatePickerColors
+constructor(
+ val selectedPickerContentColor: Color,
+ val unselectedPickerContentColor: Color,
+ val pickerLabelColor: Color,
+ val nextButtonContentColor: Color,
+ val nextButtonContainerColor: Color,
+ val confirmButtonContentColor: Color,
+ val confirmButtonContainerColor: Color,
+) {
+ internal fun copy(
+ selectedPickerContentColor: Color,
+ unselectedPickerContentColor: Color,
+ pickerLabelColor: Color,
+ nextButtonContentColor: Color,
+ nextButtonContainerColor: Color,
+ confirmButtonContentColor: Color,
+ confirmButtonContainerColor: Color,
+ ) =
+ DatePickerColors(
+ selectedPickerContentColor =
+ selectedPickerContentColor.takeOrElse { this.selectedPickerContentColor },
+ unselectedPickerContentColor =
+ unselectedPickerContentColor.takeOrElse { this.unselectedPickerContentColor },
+ pickerLabelColor = pickerLabelColor.takeOrElse { this.pickerLabelColor },
+ nextButtonContentColor =
+ nextButtonContentColor.takeOrElse { this.nextButtonContentColor },
+ nextButtonContainerColor =
+ nextButtonContainerColor.takeOrElse { this.nextButtonContainerColor },
+ confirmButtonContentColor =
+ confirmButtonContentColor.takeOrElse { this.confirmButtonContentColor },
+ confirmButtonContainerColor =
+ confirmButtonContainerColor.takeOrElse { this.confirmButtonContainerColor },
+ )
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || other !is DatePickerColors) return false
+
+ if (selectedPickerContentColor != other.selectedPickerContentColor) return false
+ if (unselectedPickerContentColor != other.unselectedPickerContentColor) return false
+ if (pickerLabelColor != other.pickerLabelColor) return false
+ if (nextButtonContentColor != other.nextButtonContentColor) return false
+ if (nextButtonContainerColor != other.nextButtonContainerColor) return false
+ if (confirmButtonContentColor != other.confirmButtonContentColor) return false
+ if (confirmButtonContainerColor != other.confirmButtonContainerColor) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = selectedPickerContentColor.hashCode()
+ result = 31 * result + unselectedPickerContentColor.hashCode()
+ result = 31 * result + pickerLabelColor.hashCode()
+ result = 31 * result + nextButtonContentColor.hashCode()
+ result = 31 * result + nextButtonContainerColor.hashCode()
+ result = 31 * result + confirmButtonContentColor.hashCode()
+ result = 31 * result + confirmButtonContainerColor.hashCode()
+
+ return result
+ }
+}
+
+/** Represents the possible column options for the DatePicker. */
+private enum class DatePickerOption {
+ Day,
+ Month,
+ Year
+}
+
+private fun DatePickerType.toDatePickerOptions() =
+ when (value) {
+ DatePickerType.YearMonthDay.value ->
+ arrayOf(DatePickerOption.Year, DatePickerOption.Month, DatePickerOption.Day)
+ DatePickerType.MonthDayYear.value ->
+ arrayOf(DatePickerOption.Month, DatePickerOption.Day, DatePickerOption.Year)
+ else -> arrayOf(DatePickerOption.Day, DatePickerOption.Month, DatePickerOption.Year)
+ }
+
+@RequiresApi(Build.VERSION_CODES.O)
+private fun verifyDates(
+ date: LocalDate,
+ fromDate: LocalDate,
+ toDate: LocalDate,
+) {
+ require(toDate >= fromDate) { "toDate should be greater than or equal to fromDate" }
+ require(date in fromDate..toDate) { "date should lie between fromDate and toDate" }
+}
+
+@RequiresApi(Build.VERSION_CODES.O)
+private fun getMonthNames(pattern: String): List<String> {
+ val monthFormatter = DateTimeFormatter.ofPattern(pattern)
+ val months = 1..12
+ return months.map { LocalDate.of(2022, it, 1).format(monthFormatter) }
+}
+
+private fun getPickerGroupRowOffset(
+ rowWidth: Dp,
+ dayPickerWidth: Dp,
+ monthPickerWidth: Dp,
+ yearPickerWidth: Dp,
+ touchExplorationServicesEnabled: Boolean,
+ pickerGroupState: PickerGroupState,
+): Dp {
+ val currentOffset = (rowWidth - (dayPickerWidth + monthPickerWidth + yearPickerWidth)) / 2
+
+ return if (touchExplorationServicesEnabled && pickerGroupState.selectedIndex < 0) {
+ ((rowWidth - dayPickerWidth) / 2) - currentOffset
+ } else if (touchExplorationServicesEnabled && pickerGroupState.selectedIndex > 2) {
+ ((rowWidth - yearPickerWidth) / 2) - (dayPickerWidth + monthPickerWidth + currentOffset)
+ } else {
+ 0.dp
+ }
+}
+
+@RequiresApi(Build.VERSION_CODES.O)
+private class DatePickerState(
+ private val date: LocalDate,
+ private val fromDate: LocalDate?,
+ private val toDate: LocalDate?,
+) {
+ // Year range 1900 - 2100 was suggested in b/277885199
+ private val startYear = fromDate?.year ?: 1900
+
+ private val numOfYears =
+ if (toDate != null) {
+ toDate.year - startYear + 1
+ } else {
+ 2100 - startYear + 1
+ }
+
+ val yearState =
+ PickerState(
+ initialNumberOfOptions = numOfYears,
+ initiallySelectedOption = date.year - startYear,
+ repeatItems = numOfYears > 2
+ )
+
+ val monthState =
+ PickerState(
+ initialNumberOfOptions = numberOfMonth,
+ initiallySelectedOption = date.monthValue - monthOptionStartMonth,
+ repeatItems = numberOfMonth > 2
+ )
+
+ val dayState =
+ PickerState(
+ initialNumberOfOptions = numberOfDay,
+ initiallySelectedOption = date.dayOfMonth - dayOptionStartDay,
+ repeatItems = numberOfDay > 2
+ )
+
+ val numberOfMonth: Int
+ get() = monthOptionEndMonth - monthOptionStartMonth + 1
+
+ val monthOptionStartMonth: Int
+ get() =
+ if (fromDate != null && selectedYearEqualsFromYear) {
+ fromDate.monthValue
+ } else {
+ 1
+ }
+
+ val monthOptionEndMonth: Int
+ get() =
+ if (toDate != null && selectedYearEqualsToYear) {
+ toDate.monthValue
+ } else {
+ 12
+ }
+
+ val numberOfDay: Int
+ get() = dayOptionEndDay - dayOptionStartDay + 1
+
+ val dayOptionStartDay: Int
+ get() =
+ if (fromDate != null && selectedMonthEqualsFromMonth) {
+ fromDate.dayOfMonth
+ } else {
+ 1
+ }
+
+ val dayOptionEndDay: Int
+ get() =
+ if (toDate != null && selectedMonthEqualsToMonth) {
+ toDate.dayOfMonth
+ } else {
+ maxDayInMonth
+ }
+
+ fun currentYear(year: Int = yearState.selectedOption): Int {
+ return year + startYear
+ }
+
+ fun currentMonth(monthIndex: Int = monthState.selectedOption): Int {
+ return monthIndex + monthOptionStartMonth
+ }
+
+ fun currentDay(day: Int = dayState.selectedOption): Int {
+ return day + dayOptionStartDay
+ }
+
+ private val selectedYearEqualsFromYear: Boolean
+ get() = fromDate?.year == currentYear()
+
+ private val selectedYearEqualsToYear: Boolean
+ get() = toDate?.year == currentYear()
+
+ private val selectedMonthEqualsFromMonth: Boolean
+ get() = selectedYearEqualsFromYear && fromDate?.monthValue == currentMonth()
+
+ private val selectedMonthEqualsToMonth: Boolean
+ get() = selectedYearEqualsToYear && toDate?.monthValue == currentMonth()
+
+ private val firstDayOfMonth: LocalDate
+ get() =
+ LocalDate.of(
+ currentYear(),
+ currentMonth(),
+ 1,
+ )
+
+ private val maxDayInMonth
+ get() = firstDayOfMonth.with(TemporalAdjusters.lastDayOfMonth()).dayOfMonth
+}
+
+private fun createDescriptionDatePicker(
+ pickerGroupState: PickerGroupState,
+ selectedValue: Int,
+ label: String,
+): String {
+ return when (pickerGroupState.selectedIndex) {
+ NoneSelectedIndex -> label
+ else -> "$label, $selectedValue"
+ }
+}
+
+private suspend fun adjustOptionSelection(
+ prevStartState: MutableIntState,
+ currentStartValue: Int,
+ currentNumberOfOptions: Int,
+ pickerState: PickerState
+) {
+ val prevStartValue = prevStartState.intValue
+ val prevSelectedOption = pickerState.selectedOption
+ val prevSelectedValue = prevSelectedOption + prevStartValue
+ val prevNumberOfOptions: Int = pickerState.numberOfOptions
+ // Update picker's number of options if changed.
+ if (currentNumberOfOptions != prevNumberOfOptions) {
+ pickerState.numberOfOptions = currentNumberOfOptions
+ }
+ when {
+ currentStartValue != prevStartValue && prevStartValue != 1 -> { // Scrolled from `fromDate`
+ val prevSelectedValueIndex = prevSelectedValue - 1
+ // Check if previous value still exists in current options.
+ if (prevSelectedValueIndex < currentNumberOfOptions) {
+ // Scroll to the index which has the same value with the previous value.
+ pickerState.scrollToOption(prevSelectedValueIndex)
+ } else {
+ // Scroll to the closet value to the previous value.
+ pickerState.scrollToOption(currentNumberOfOptions - 1)
+ }
+ prevStartState.intValue = currentStartValue
+ }
+ currentStartValue != 1 -> { // Scrolled to `fromDate`
+ val currentValueIndex =
+ if (prevSelectedValue >= currentStartValue) {
+ // Scroll to the index which has the same value with the previous value.
+ prevSelectedValue - currentStartValue
+ } else {
+ // Scroll to the closet value to the previous value.
+ 0
+ }
+ pickerState.scrollToOption(currentValueIndex)
+ prevStartState.intValue = currentStartValue
+ }
+ currentNumberOfOptions != prevNumberOfOptions -> { // Only number of options changed.
+ if (prevSelectedOption >= currentNumberOfOptions) {
+ // Scroll to the closet value to the previous value.
+ pickerState.animateScrollToOption(currentNumberOfOptions - 1)
+ }
+ }
+ }
+}
+
+private const val NoneSelectedIndex = -1
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/dialog/Dialog.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Dialog.kt
similarity index 97%
rename from wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/dialog/Dialog.kt
rename to wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Dialog.kt
index d42ba2d..f5c6dc6 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/dialog/Dialog.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Dialog.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.wear.compose.material3.dialog
+package androidx.wear.compose.material3
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.Transition
@@ -34,9 +34,6 @@
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.window.DialogProperties
import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
-import androidx.wear.compose.material3.MaterialTheme
-import androidx.wear.compose.material3.ScreenScaffold
-import androidx.wear.compose.material3.SwipeToDismissBox
import androidx.wear.compose.material3.tokens.MotionTokens
/**
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/FontScaleIndependent.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/FontScaleIndependent.kt
new file mode 100644
index 0000000..6ab4c35
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/FontScaleIndependent.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+
+@Composable
+internal fun FontScaleIndependent(content: @Composable () -> Unit) {
+ CompositionLocalProvider(
+ value =
+ LocalDensity provides
+ Density(
+ density = LocalDensity.current.density,
+ fontScale = 1f,
+ ),
+ content = content
+ )
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
index 9467d0e..1d4e8e4 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.InteractionSource
@@ -27,6 +29,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
@@ -37,8 +40,10 @@
import androidx.wear.compose.material3.tokens.FilledTonalIconButtonTokens
import androidx.wear.compose.material3.tokens.IconButtonTokens
import androidx.wear.compose.material3.tokens.IconToggleButtonTokens
+import androidx.wear.compose.material3.tokens.MotionTokens
import androidx.wear.compose.material3.tokens.OutlinedIconButtonTokens
import androidx.wear.compose.material3.tokens.ShapeTokens
+import androidx.wear.compose.materialcore.animateSelectionColor
/**
* Wear Material [IconButton] is a circular, icon-only button with transparent background and no
@@ -68,6 +73,10 @@
* Example of an [IconButton] with shape animation of rounded corners on press:
*
* @sample androidx.wear.compose.material3.samples.IconButtonWithCornerAnimationSample
+ *
+ * Example of an [IconButton] with image content:
+ *
+ * @sample androidx.wear.compose.material3.samples.IconButtonWithImageSample
* @param onClick Will be called when the user clicks the button.
* @param modifier Modifier to be applied to the button.
* @param onLongClick Called when this button is long clicked (long-pressed). When this callback is
@@ -351,8 +360,8 @@
* @param modifier Modifier to be applied to the toggle button.
* @param enabled Controls the enabled state of the toggle button. When `false`, this toggle button
* will not be clickable.
- * @param colors [ToggleButtonColors] that will be used to resolve the container and content color
- * for this toggle button.
+ * @param colors [IconToggleButtonColors] that will be used to resolve the container and content
+ * color for this toggle button.
* @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
* emitting [Interaction]s for this button. You can use this to change the button's appearance or
* preview the button in different states. Note that if `null` is provided, interactions will
@@ -368,7 +377,7 @@
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
- colors: ToggleButtonColors = IconButtonDefaults.iconToggleButtonColors(),
+ colors: IconToggleButtonColors = IconButtonDefaults.iconToggleButtonColors(),
interactionSource: MutableInteractionSource? = null,
shape: Shape = IconButtonDefaults.shape,
border: BorderStroke? = null,
@@ -402,6 +411,9 @@
val pressedShape: CornerBasedShape
@Composable get() = MaterialTheme.shapes.small
+ /** Recommended alpha to apply to an IconButton with Image content with disabled */
+ val disabledImageOpacity = DisabledContentAlpha
+
/**
* Creates a [Shape] with a animation between two CornerBasedShapes.
*
@@ -599,7 +611,7 @@
)
/**
- * Creates a [ToggleButtonColors] for a [IconToggleButton]
+ * Creates an [IconToggleButtonColors] for a [IconToggleButton]
* - by default, a colored background with a contrasting content color.
*
* If the button is disabled, then the colors will have an alpha ([DisabledContentAlpha] and
@@ -609,7 +621,7 @@
fun iconToggleButtonColors() = MaterialTheme.colorScheme.defaultIconToggleButtonColors
/**
- * Creates a [ToggleButtonColors] for a [IconToggleButton]
+ * Creates a [IconToggleButtonColors] for a [IconToggleButton]
* - by default, a colored background with a contrasting content color.
*
* If the button is disabled, then the colors will have an alpha ([DisabledContentAlpha] and
@@ -642,7 +654,7 @@
disabledCheckedContentColor: Color = Color.Unspecified,
disabledUncheckedContainerColor: Color = Color.Unspecified,
disabledUncheckedContentColor: Color = Color.Unspecified,
- ): ToggleButtonColors =
+ ): IconToggleButtonColors =
MaterialTheme.colorScheme.defaultIconToggleButtonColors.copy(
checkedContainerColor = checkedContainerColor,
checkedContentColor = checkedContentColor,
@@ -790,10 +802,10 @@
.also { defaultIconButtonColorsCached = it }
}
- private val ColorScheme.defaultIconToggleButtonColors: ToggleButtonColors
+ private val ColorScheme.defaultIconToggleButtonColors: IconToggleButtonColors
get() {
return defaultIconToggleButtonColorsCached
- ?: ToggleButtonColors(
+ ?: IconToggleButtonColors(
checkedContainerColor =
fromToken(IconToggleButtonTokens.CheckedContainerColor),
checkedContentColor = fromToken(IconToggleButtonTokens.CheckedContentColor),
@@ -907,3 +919,129 @@
return result
}
}
+
+/**
+ * Represents the different container and content colors used for [IconToggleButton] in various
+ * states, that are checked, unchecked, enabled and disabled.
+ *
+ * @param checkedContainerColor Container or background color when the toggle button is checked
+ * @param checkedContentColor Color of the content (text or icon) when the toggle button is checked
+ * @param uncheckedContainerColor Container or background color when the toggle button is unchecked
+ * @param uncheckedContentColor Color of the content (text or icon) when the toggle button is
+ * unchecked
+ * @param disabledCheckedContainerColor Container or background color when the toggle button is
+ * disabled and checked
+ * @param disabledCheckedContentColor Color of content (text or icon) when the toggle button is
+ * disabled and checked
+ * @param disabledUncheckedContainerColor Container or background color when the toggle button is
+ * disabled and unchecked
+ * @param disabledUncheckedContentColor Color of the content (text or icon) when the toggle button
+ * is disabled and unchecked
+ */
+@Immutable
+class IconToggleButtonColors(
+ val checkedContainerColor: Color,
+ val checkedContentColor: Color,
+ val uncheckedContainerColor: Color,
+ val uncheckedContentColor: Color,
+ val disabledCheckedContainerColor: Color,
+ val disabledCheckedContentColor: Color,
+ val disabledUncheckedContainerColor: Color,
+ val disabledUncheckedContentColor: Color,
+) {
+ internal fun copy(
+ checkedContainerColor: Color,
+ checkedContentColor: Color,
+ uncheckedContainerColor: Color,
+ uncheckedContentColor: Color,
+ disabledCheckedContainerColor: Color,
+ disabledCheckedContentColor: Color,
+ disabledUncheckedContainerColor: Color,
+ disabledUncheckedContentColor: Color,
+ ): IconToggleButtonColors =
+ IconToggleButtonColors(
+ checkedContainerColor = checkedContainerColor.takeOrElse { this.checkedContainerColor },
+ checkedContentColor = checkedContentColor.takeOrElse { this.checkedContentColor },
+ uncheckedContainerColor =
+ uncheckedContainerColor.takeOrElse { this.uncheckedContainerColor },
+ uncheckedContentColor = uncheckedContentColor.takeOrElse { this.uncheckedContentColor },
+ disabledCheckedContainerColor =
+ disabledCheckedContainerColor.takeOrElse { this.disabledCheckedContainerColor },
+ disabledCheckedContentColor =
+ disabledCheckedContentColor.takeOrElse { this.disabledCheckedContentColor },
+ disabledUncheckedContainerColor =
+ disabledUncheckedContainerColor.takeOrElse { this.disabledUncheckedContainerColor },
+ disabledUncheckedContentColor =
+ disabledUncheckedContentColor.takeOrElse { this.disabledUncheckedContentColor },
+ )
+
+ /**
+ * Determines the container color based on whether the toggle button is [enabled] and [checked].
+ *
+ * @param enabled Whether the toggle button is enabled
+ * @param checked Whether the toggle button is checked
+ */
+ @Composable
+ internal fun containerColor(enabled: Boolean, checked: Boolean): State<Color> =
+ animateSelectionColor(
+ enabled = enabled,
+ checked = checked,
+ checkedColor = checkedContainerColor,
+ uncheckedColor = uncheckedContainerColor,
+ disabledCheckedColor = disabledCheckedContainerColor,
+ disabledUncheckedColor = disabledUncheckedContainerColor,
+ animationSpec = COLOR_ANIMATION_SPEC
+ )
+
+ /**
+ * Determines the content color based on whether the toggle button is [enabled] and [checked].
+ *
+ * @param enabled Whether the toggle button is enabled
+ * @param checked Whether the toggle button is checked
+ */
+ @Composable
+ internal fun contentColor(enabled: Boolean, checked: Boolean): State<Color> =
+ animateSelectionColor(
+ enabled = enabled,
+ checked = checked,
+ checkedColor = checkedContentColor,
+ uncheckedColor = uncheckedContentColor,
+ disabledCheckedColor = disabledCheckedContentColor,
+ disabledUncheckedColor = disabledUncheckedContentColor,
+ animationSpec = COLOR_ANIMATION_SPEC
+ )
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (this::class != other::class) return false
+
+ other as IconToggleButtonColors
+
+ if (checkedContainerColor != other.checkedContainerColor) return false
+ if (checkedContentColor != other.checkedContentColor) return false
+ if (uncheckedContainerColor != other.uncheckedContainerColor) return false
+ if (uncheckedContentColor != other.uncheckedContentColor) return false
+ if (disabledCheckedContainerColor != other.disabledCheckedContainerColor) return false
+ if (disabledCheckedContentColor != other.disabledCheckedContentColor) return false
+ if (disabledUncheckedContainerColor != other.disabledUncheckedContainerColor) return false
+ if (disabledUncheckedContentColor != other.disabledUncheckedContentColor) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = checkedContainerColor.hashCode()
+ result = 31 * result + checkedContentColor.hashCode()
+ result = 31 * result + uncheckedContainerColor.hashCode()
+ result = 31 * result + uncheckedContentColor.hashCode()
+ result = 31 * result + disabledCheckedContainerColor.hashCode()
+ result = 31 * result + disabledCheckedContentColor.hashCode()
+ result = 31 * result + disabledUncheckedContainerColor.hashCode()
+ result = 31 * result + disabledUncheckedContentColor.hashCode()
+ return result
+ }
+}
+
+private val COLOR_ANIMATION_SPEC: AnimationSpec<Color> =
+ tween(MotionTokens.DurationMedium1, 0, MotionTokens.EasingStandardDecelerate)
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/LevelIndicator.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/LevelIndicator.kt
index 39136ea..6b112ae 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/LevelIndicator.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/LevelIndicator.kt
@@ -154,7 +154,7 @@
const val SweepAngle = 72f
/** The default stroke width for the indicator and track strokes */
- val StrokeWidth = 5.dp
+ val StrokeWidth = 6.dp
internal val edgePadding = PaddingDefaults.edgePadding
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/OpenOnPhoneDialog.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/OpenOnPhoneDialog.kt
new file mode 100644
index 0000000..c7944ef
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/OpenOnPhoneDialog.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalAccessibilityManager
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.DialogProperties
+import androidx.wear.compose.foundation.CurvedDirection
+import androidx.wear.compose.foundation.CurvedLayout
+import androidx.wear.compose.foundation.CurvedModifier
+import androidx.wear.compose.foundation.CurvedScope
+import androidx.wear.compose.foundation.CurvedTextStyle
+import androidx.wear.compose.foundation.padding
+import androidx.wear.compose.material3.tokens.ColorSchemeKeyTokens
+import androidx.wear.compose.materialcore.screenHeightDp
+import androidx.wear.compose.materialcore.screenWidthDp
+import kotlinx.coroutines.delay
+
+/**
+ * A full-screen dialog that displays an animated icon with a curved text at the bottom.
+ *
+ * The dialog will be showing a message to the user for [durationMillis]. After a specified timeout,
+ * the [onDismissRequest] callback will be invoked, where it's up to the caller to handle the
+ * dismissal. To hide the dialog, [show] parameter should be set to false.
+ *
+ * This dialog is typically used to indicate that an action has been initiated and will continue on
+ * the user's phone. Once this dialog is displayed, it's developer responsibility to establish the
+ * connection between the watch and the phone.
+ *
+ * Example of an [OpenOnPhoneDialog] usage:
+ *
+ * @sample androidx.wear.compose.material3.samples.OpenOnPhoneDialogSample
+ * @param show A boolean indicating whether the dialog should be displayed.
+ * @param onDismissRequest A lambda function to be called when the dialog is dismissed - either by
+ * swiping right or when the [durationMillis] has passed.
+ * @param modifier Modifier to be applied to the dialog content.
+ * @param curvedText A slot for displaying curved text content which will be shown along the bottom
+ * edge of the dialog. Defaults to a localized open on phone message.
+ * @param colors [OpenOnPhoneDialogColors] that will be used to resolve the colors used for this
+ * [OpenOnPhoneDialog].
+ * @param properties An optional [DialogProperties] object for configuring the dialog's behavior.
+ * @param durationMillis The duration in milliseconds for which the dialog is displayed. Defaults to
+ * [OpenOnPhoneDialogDefaults.DurationMillis].
+ * @param content A slot for displaying an icon inside the open on phone dialog, which can be
+ * animated. Defaults to [OpenOnPhoneDialogDefaults.Icon].
+ */
+@Composable
+fun OpenOnPhoneDialog(
+ show: Boolean,
+ onDismissRequest: () -> Unit,
+ modifier: Modifier = Modifier,
+ curvedText: (CurvedScope.() -> Unit)? = OpenOnPhoneDialogDefaults.curvedText(),
+ colors: OpenOnPhoneDialogColors = OpenOnPhoneDialogDefaults.colors(),
+ properties: DialogProperties = DialogProperties(),
+ durationMillis: Long = OpenOnPhoneDialogDefaults.DurationMillis,
+ content: @Composable BoxScope.() -> Unit = OpenOnPhoneDialogDefaults.Icon,
+) {
+ var progress by remember(show) { mutableFloatStateOf(0f) }
+ val animatable = remember { Animatable(0f) }
+
+ val a11yDurationMillis =
+ LocalAccessibilityManager.current?.calculateRecommendedTimeoutMillis(
+ originalTimeoutMillis = durationMillis,
+ containsIcons = true,
+ containsText = curvedText != null,
+ containsControls = false,
+ ) ?: durationMillis
+
+ LaunchedEffect(show, a11yDurationMillis) {
+ if (show) {
+ animatable.snapTo(0f)
+ animatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(durationMillis = a11yDurationMillis.toInt(), easing = LinearEasing),
+ ) {
+ progress = value
+ }
+ onDismissRequest()
+ }
+ }
+
+ Dialog(
+ showDialog = show,
+ modifier = modifier,
+ onDismissRequest = onDismissRequest,
+ properties = properties,
+ ) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ val bottomPadding =
+ if (curvedText != null)
+ screenHeightDp() * OpenOnPhoneDialogDefaults.ExtraBottomPaddingFraction
+ else 0f
+ Box(
+ Modifier.fillMaxSize().padding(bottom = bottomPadding.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ iconContainer(
+ iconContainerColor = colors.iconContainerColor,
+ progressIndicatorColors =
+ ProgressIndicatorColors(
+ SolidColor(colors.progressIndicatorColor),
+ SolidColor(colors.progressTrackColor)
+ ),
+ progress = { progress }
+ )()
+ CompositionLocalProvider(LocalContentColor provides colors.iconColor) { content() }
+ }
+ CompositionLocalProvider(LocalContentColor provides colors.textColor) {
+ curvedText?.let { CurvedLayout(anchor = 90f, contentBuilder = curvedText) }
+ }
+ }
+ }
+}
+
+/** Contains the default values used by [OpenOnPhoneDialog]. */
+object OpenOnPhoneDialogDefaults {
+
+ /**
+ * A default composable used in [OpenOnPhoneDialog] that displays an open on phone icon with an
+ * animation.
+ */
+ @OptIn(ExperimentalAnimationGraphicsApi::class)
+ val Icon: @Composable BoxScope.() -> Unit = {
+ val animation =
+ AnimatedImageVector.animatedVectorResource(R.drawable.open_on_phone_animation)
+ var atEnd by remember { mutableStateOf(false) }
+ LaunchedEffect(Unit) {
+ delay(IconDelay)
+ atEnd = true
+ }
+ Icon(
+ painter = rememberAnimatedVectorPainter(animation, atEnd),
+ contentDescription = null,
+ modifier = Modifier.size(IconSize).align(Alignment.Center),
+ )
+ }
+
+ /**
+ * A default composable that displays text along a curved path, used in [OpenOnPhoneDialog].
+ *
+ * @param text The text to display. Defaults to an open on phone message.
+ * @param style The style to apply to the text. Defaults to
+ * CurvedTextStyle(MaterialTheme.typography.titleLarge).
+ */
+ @Composable
+ fun curvedText(
+ text: String = LocalContext.current.resources.getString(R.string.wear_m3c_open_on_phone),
+ style: CurvedTextStyle = CurvedTextStyle(MaterialTheme.typography.titleLarge)
+ ): CurvedScope.() -> Unit = {
+ curvedText(
+ text = text,
+ style = style,
+ maxSweepAngle = CurvedTextDefaults.StaticContentMaxSweepAngle,
+ modifier = CurvedModifier.padding(PaddingDefaults.edgePadding),
+ angularDirection = CurvedDirection.Angular.Reversed
+ )
+ }
+
+ /**
+ * Creates a [OpenOnPhoneDialogColors] that represents the default colors used in
+ * [OpenOnPhoneDialog].
+ */
+ @Composable fun colors() = MaterialTheme.colorScheme.defaultOpenOnPhoneDialogColors
+
+ /**
+ * Creates a [OpenOnPhoneDialogColors] with modified colors used in [OpenOnPhoneDialog].
+ *
+ * @param iconColor The icon color.
+ * @param iconContainerColor The icon container color.
+ * @param progressIndicatorColor The progress indicator color.
+ * @param progressTrackColor The progress track color.
+ * @param textColor The text color.
+ */
+ @Composable
+ fun colors(
+ iconColor: Color = Color.Unspecified,
+ iconContainerColor: Color = Color.Unspecified,
+ progressIndicatorColor: Color = Color.Unspecified,
+ progressTrackColor: Color = Color.Unspecified,
+ textColor: Color = Color.Unspecified
+ ) =
+ MaterialTheme.colorScheme.defaultOpenOnPhoneDialogColors.copy(
+ iconColor = iconColor,
+ iconContainerColor = iconContainerColor,
+ progressIndicatorColor = progressIndicatorColor,
+ progressTrackColor = progressTrackColor,
+ textColor = textColor
+ )
+
+ /** Default timeout for the [OpenOnPhoneDialog] dialog, in milliseconds. */
+ const val DurationMillis = 4000L
+
+ internal val IconDelay = 67L
+ internal val SizeFraction = 0.6f
+ internal val ExtraBottomPaddingFraction = 0.02f
+ internal val IconSize = 52.dp
+
+ internal val progressIndicatorStrokeWidth = 5.dp
+ internal val progressIndicatorPadding = 5.dp
+
+ private val ColorScheme.defaultOpenOnPhoneDialogColors: OpenOnPhoneDialogColors
+ get() {
+ return mDefaultOpenOnPhoneDialogColorsCached
+ ?: OpenOnPhoneDialogColors(
+ iconColor = fromToken(ColorSchemeKeyTokens.Primary),
+ iconContainerColor = fromToken(ColorSchemeKeyTokens.PrimaryContainer),
+ progressIndicatorColor = fromToken(ColorSchemeKeyTokens.Primary),
+ progressTrackColor = fromToken(ColorSchemeKeyTokens.OnPrimary),
+ textColor = fromToken(ColorSchemeKeyTokens.OnBackground)
+ )
+ .also { mDefaultOpenOnPhoneDialogColorsCached = it }
+ }
+}
+
+/**
+ * Represents the colors used in [OpenOnPhoneDialog].
+ *
+ * @param iconColor Color used to tint the icon.
+ * @param iconContainerColor The color of the container behind the icon.
+ * @param progressIndicatorColor Color used to draw the indicator arc of progress indicator.
+ * @param progressTrackColor Color used to draw the track of progress indicator.
+ * @param textColor Color used to draw the text.
+ */
+class OpenOnPhoneDialogColors(
+ val iconColor: Color,
+ val iconContainerColor: Color,
+ val progressIndicatorColor: Color,
+ val progressTrackColor: Color,
+ val textColor: Color
+) {
+ internal fun copy(
+ iconColor: Color? = null,
+ iconContainerColor: Color? = null,
+ progressIndicatorColor: Color? = null,
+ progressTrackColor: Color? = null,
+ textColor: Color? = null
+ ) =
+ OpenOnPhoneDialogColors(
+ iconColor = iconColor ?: this.iconColor,
+ iconContainerColor = iconContainerColor ?: this.iconContainerColor,
+ progressIndicatorColor = progressIndicatorColor ?: this.progressIndicatorColor,
+ progressTrackColor = progressTrackColor ?: this.progressTrackColor,
+ textColor = textColor ?: this.textColor
+ )
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || other !is OpenOnPhoneDialogColors) return false
+
+ if (iconColor != other.iconColor) return false
+ if (iconContainerColor != other.iconContainerColor) return false
+ if (progressIndicatorColor != other.progressIndicatorColor) return false
+ if (progressTrackColor != other.progressTrackColor) return false
+ if (textColor != other.textColor) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = iconColor.hashCode()
+ result = 31 * result + iconContainerColor.hashCode()
+ result = 31 * result + progressIndicatorColor.hashCode()
+ result = 31 * result + progressTrackColor.hashCode()
+ result = 31 * result + textColor.hashCode()
+ return result
+ }
+}
+
+private fun iconContainer(
+ iconContainerColor: Color,
+ progressIndicatorColors: ProgressIndicatorColors,
+ progress: () -> Float
+): @Composable BoxScope.() -> Unit = {
+ val size = screenWidthDp() * OpenOnPhoneDialogDefaults.SizeFraction
+ Box(Modifier.size(size.dp)) {
+ Box(
+ Modifier.fillMaxSize()
+ .padding(
+ OpenOnPhoneDialogDefaults.progressIndicatorStrokeWidth +
+ OpenOnPhoneDialogDefaults.progressIndicatorPadding
+ )
+ .graphicsLayer {
+ shape = CircleShape
+ clip = true
+ }
+ .background(iconContainerColor)
+ )
+
+ CircularProgressIndicator(
+ progress = progress,
+ strokeWidth = OpenOnPhoneDialogDefaults.progressIndicatorStrokeWidth,
+ colors = progressIndicatorColors
+ )
+ }
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt
index e5c965c..1eb3d51 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt
@@ -30,7 +30,10 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
@@ -59,6 +62,7 @@
import androidx.compose.ui.semantics.focused
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.scrollToIndex
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -546,4 +550,31 @@
get() = pickerState.selectedOption
}
+internal fun pickerTextOption(
+ textStyle: TextStyle,
+ indexToText: (Int) -> String,
+ optionHeight: Dp,
+ selectedContentColor: Color,
+ unselectedContentColor: Color,
+): (@Composable PickerScope.(optionIndex: Int, pickerSelected: Boolean) -> Unit) =
+ { value: Int, pickerSelected: Boolean ->
+ Box(
+ modifier = Modifier.fillMaxSize().height(optionHeight),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = indexToText(value),
+ maxLines = 1,
+ style = textStyle,
+ color =
+ if (pickerSelected) {
+ selectedContentColor
+ } else {
+ unselectedContentColor
+ },
+ modifier = Modifier.align(Alignment.Center).wrapContentSize(),
+ )
+ }
+ }
+
private const val LARGE_NUMBER_OF_ITEMS = 100_000_000
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
index c559aa5..20e8047 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.InteractionSource
@@ -28,16 +30,19 @@
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.takeOrElse
import androidx.wear.compose.material3.tokens.FilledTextButtonTokens
import androidx.wear.compose.material3.tokens.FilledTonalTextButtonTokens
+import androidx.wear.compose.material3.tokens.MotionTokens
import androidx.wear.compose.material3.tokens.OutlinedTextButtonTokens
import androidx.wear.compose.material3.tokens.ShapeTokens
import androidx.wear.compose.material3.tokens.TextButtonTokens
import androidx.wear.compose.material3.tokens.TextToggleButtonTokens
+import androidx.wear.compose.materialcore.animateSelectionColor
/**
* Wear Material [TextButton] is a circular, text-only button with transparent background and no
@@ -170,7 +175,7 @@
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
- colors: ToggleButtonColors = TextButtonDefaults.textToggleButtonColors(),
+ colors: TextToggleButtonColors = TextButtonDefaults.textToggleButtonColors(),
interactionSource: MutableInteractionSource? = null,
shape: Shape = TextButtonDefaults.shape,
border: BorderStroke? = null,
@@ -398,7 +403,7 @@
)
/**
- * Creates a [ToggleButtonColors] for a [TextToggleButton]
+ * Creates a [TextToggleButtonColors] for a [TextToggleButton]
* - by default, a colored background with a contrasting content color. If the button is
* disabled, then the colors will have an alpha ([DisabledContainerAlpha] or
* [DisabledContentAlpha]) value applied.
@@ -407,7 +412,7 @@
fun textToggleButtonColors() = MaterialTheme.colorScheme.defaultTextToggleButtonColors
/**
- * Creates a [ToggleButtonColors] for a [TextToggleButton]
+ * Creates a [TextToggleButtonColors] for a [TextToggleButton]
* - by default, a colored background with a contrasting content color. If the button is
* disabled, then the colors will have an alpha ([DisabledContainerAlpha] or
* [DisabledContentAlpha]) value applied.
@@ -439,7 +444,7 @@
disabledCheckedContentColor: Color = Color.Unspecified,
disabledUncheckedContainerColor: Color = Color.Unspecified,
disabledUncheckedContentColor: Color = Color.Unspecified,
- ): ToggleButtonColors =
+ ): TextToggleButtonColors =
MaterialTheme.colorScheme.defaultTextToggleButtonColors.copy(
checkedContainerColor = checkedContainerColor,
checkedContentColor = checkedContentColor,
@@ -575,10 +580,10 @@
.also { defaultTextButtonColorsCached = it }
}
- private val ColorScheme.defaultTextToggleButtonColors: ToggleButtonColors
+ private val ColorScheme.defaultTextToggleButtonColors: TextToggleButtonColors
get() {
return defaultTextToggleButtonColorsCached
- ?: ToggleButtonColors(
+ ?: TextToggleButtonColors(
checkedContainerColor =
fromToken(TextToggleButtonTokens.CheckedContainerColor),
checkedContentColor = fromToken(TextToggleButtonTokens.CheckedContentColor),
@@ -691,3 +696,129 @@
return result
}
}
+
+/**
+ * Represents the different container and content colors used for [TextToggleButton] in various
+ * states, that are checked, unchecked, enabled and disabled.
+ *
+ * @param checkedContainerColor Container or background color when the toggle button is checked
+ * @param checkedContentColor Color of the content (text or icon) when the toggle button is checked
+ * @param uncheckedContainerColor Container or background color when the toggle button is unchecked
+ * @param uncheckedContentColor Color of the content (text or icon) when the toggle button is
+ * unchecked
+ * @param disabledCheckedContainerColor Container or background color when the toggle button is
+ * disabled and checked
+ * @param disabledCheckedContentColor Color of content (text or icon) when the toggle button is
+ * disabled and checked
+ * @param disabledUncheckedContainerColor Container or background color when the toggle button is
+ * disabled and unchecked
+ * @param disabledUncheckedContentColor Color of the content (text or icon) when the toggle button
+ * is disabled and unchecked
+ */
+@Immutable
+class TextToggleButtonColors(
+ val checkedContainerColor: Color,
+ val checkedContentColor: Color,
+ val uncheckedContainerColor: Color,
+ val uncheckedContentColor: Color,
+ val disabledCheckedContainerColor: Color,
+ val disabledCheckedContentColor: Color,
+ val disabledUncheckedContainerColor: Color,
+ val disabledUncheckedContentColor: Color,
+) {
+ internal fun copy(
+ checkedContainerColor: Color,
+ checkedContentColor: Color,
+ uncheckedContainerColor: Color,
+ uncheckedContentColor: Color,
+ disabledCheckedContainerColor: Color,
+ disabledCheckedContentColor: Color,
+ disabledUncheckedContainerColor: Color,
+ disabledUncheckedContentColor: Color,
+ ): TextToggleButtonColors =
+ TextToggleButtonColors(
+ checkedContainerColor = checkedContainerColor.takeOrElse { this.checkedContainerColor },
+ checkedContentColor = checkedContentColor.takeOrElse { this.checkedContentColor },
+ uncheckedContainerColor =
+ uncheckedContainerColor.takeOrElse { this.uncheckedContainerColor },
+ uncheckedContentColor = uncheckedContentColor.takeOrElse { this.uncheckedContentColor },
+ disabledCheckedContainerColor =
+ disabledCheckedContainerColor.takeOrElse { this.disabledCheckedContainerColor },
+ disabledCheckedContentColor =
+ disabledCheckedContentColor.takeOrElse { this.disabledCheckedContentColor },
+ disabledUncheckedContainerColor =
+ disabledUncheckedContainerColor.takeOrElse { this.disabledUncheckedContainerColor },
+ disabledUncheckedContentColor =
+ disabledUncheckedContentColor.takeOrElse { this.disabledUncheckedContentColor },
+ )
+
+ /**
+ * Determines the container color based on whether the toggle button is [enabled] and [checked].
+ *
+ * @param enabled Whether the toggle button is enabled
+ * @param checked Whether the toggle button is checked
+ */
+ @Composable
+ internal fun containerColor(enabled: Boolean, checked: Boolean): State<Color> =
+ animateSelectionColor(
+ enabled = enabled,
+ checked = checked,
+ checkedColor = checkedContainerColor,
+ uncheckedColor = uncheckedContainerColor,
+ disabledCheckedColor = disabledCheckedContainerColor,
+ disabledUncheckedColor = disabledUncheckedContainerColor,
+ animationSpec = COLOR_ANIMATION_SPEC
+ )
+
+ /**
+ * Determines the content color based on whether the toggle button is [enabled] and [checked].
+ *
+ * @param enabled Whether the toggle button is enabled
+ * @param checked Whether the toggle button is checked
+ */
+ @Composable
+ internal fun contentColor(enabled: Boolean, checked: Boolean): State<Color> =
+ animateSelectionColor(
+ enabled = enabled,
+ checked = checked,
+ checkedColor = checkedContentColor,
+ uncheckedColor = uncheckedContentColor,
+ disabledCheckedColor = disabledCheckedContentColor,
+ disabledUncheckedColor = disabledUncheckedContentColor,
+ animationSpec = COLOR_ANIMATION_SPEC
+ )
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (this::class != other::class) return false
+
+ other as TextToggleButtonColors
+
+ if (checkedContainerColor != other.checkedContainerColor) return false
+ if (checkedContentColor != other.checkedContentColor) return false
+ if (uncheckedContainerColor != other.uncheckedContainerColor) return false
+ if (uncheckedContentColor != other.uncheckedContentColor) return false
+ if (disabledCheckedContainerColor != other.disabledCheckedContainerColor) return false
+ if (disabledCheckedContentColor != other.disabledCheckedContentColor) return false
+ if (disabledUncheckedContainerColor != other.disabledUncheckedContainerColor) return false
+ if (disabledUncheckedContentColor != other.disabledUncheckedContentColor) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = checkedContainerColor.hashCode()
+ result = 31 * result + checkedContentColor.hashCode()
+ result = 31 * result + uncheckedContainerColor.hashCode()
+ result = 31 * result + uncheckedContentColor.hashCode()
+ result = 31 * result + disabledCheckedContainerColor.hashCode()
+ result = 31 * result + disabledCheckedContentColor.hashCode()
+ result = 31 * result + disabledUncheckedContainerColor.hashCode()
+ result = 31 * result + disabledUncheckedContentColor.hashCode()
+ return result
+ }
+}
+
+private val COLOR_ANIMATION_SPEC: AnimationSpec<Color> =
+ tween(MotionTokens.DurationMedium1, 0, MotionTokens.EasingStandardDecelerate)
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
index 9975891..a6c2b49 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
@@ -38,7 +38,6 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
@@ -60,7 +59,6 @@
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
@@ -223,8 +221,9 @@
option =
pickerTextOption(
textStyle = styles.optionTextStyle,
- selectedPickerColor = colors.selectedPickerContentColor,
- unselectedPickerColor = colors.unselectedPickerContentColor,
+ selectedContentColor = colors.selectedPickerContentColor,
+ unselectedContentColor =
+ colors.unselectedPickerContentColor,
indexToText = {
"%02d".format(if (is12hour) it + 1 else it)
},
@@ -250,8 +249,9 @@
pickerTextOption(
textStyle = styles.optionTextStyle,
indexToText = { "%02d".format(it) },
- selectedPickerColor = colors.selectedPickerContentColor,
- unselectedPickerColor = colors.unselectedPickerContentColor,
+ selectedContentColor = colors.selectedPickerContentColor,
+ unselectedContentColor =
+ colors.unselectedPickerContentColor,
optionHeight = styles.optionHeight,
),
spacing = styles.optionSpacing
@@ -274,8 +274,9 @@
pickerTextOption(
textStyle = styles.optionTextStyle,
indexToText = thirdPicker.indexToText,
- selectedPickerColor = colors.selectedPickerContentColor,
- unselectedPickerColor = colors.unselectedPickerContentColor,
+ selectedContentColor = colors.selectedPickerContentColor,
+ unselectedContentColor =
+ colors.unselectedPickerContentColor,
optionHeight = styles.optionHeight,
),
spacing = styles.optionSpacing
@@ -690,33 +691,6 @@
}
}
-private fun pickerTextOption(
- textStyle: TextStyle,
- selectedPickerColor: Color,
- unselectedPickerColor: Color,
- indexToText: (Int) -> String,
- optionHeight: Dp,
-): (@Composable PickerScope.(optionIndex: Int, pickerSelected: Boolean) -> Unit) =
- { value: Int, pickerSelected: Boolean ->
- Box(
- modifier = Modifier.fillMaxSize().height(optionHeight),
- contentAlignment = Alignment.Center
- ) {
- Text(
- text = indexToText(value),
- maxLines = 1,
- style = textStyle,
- color =
- if (pickerSelected) {
- selectedPickerColor
- } else {
- unselectedPickerColor
- },
- modifier = Modifier.align(Alignment.Center).wrapContentSize(),
- )
- }
- }
-
@Composable
private fun createDescription(
pickerGroupState: PickerGroupState,
@@ -729,19 +703,6 @@
else -> getPlurals(plurals, selectedValue, selectedValue)
}
-@Composable
-private fun FontScaleIndependent(content: @Composable () -> Unit) {
- CompositionLocalProvider(
- value =
- LocalDensity provides
- Density(
- density = LocalDensity.current.density,
- fontScale = 1f,
- ),
- content = content
- )
-}
-
private enum class FocusableElementsTimePicker(val index: Int) {
HOURS(0),
MINUTES(1),
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt
index ba89724..5702c0e 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt
@@ -22,6 +22,7 @@
import android.content.IntentFilter
import android.text.format.DateFormat
import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
@@ -29,6 +30,7 @@
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
@@ -43,6 +45,7 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
@@ -57,6 +60,7 @@
import androidx.wear.compose.foundation.CurvedModifier
import androidx.wear.compose.foundation.CurvedScope
import androidx.wear.compose.foundation.CurvedTextStyle
+import androidx.wear.compose.foundation.background
import androidx.wear.compose.foundation.curvedComposable
import androidx.wear.compose.foundation.curvedRow
import androidx.wear.compose.foundation.padding
@@ -115,6 +119,7 @@
content: TimeTextScope.() -> Unit
) {
val timeText = timeSource.currentTime()
+ val backgroundColor = CurvedTextDefaults.backgroundColor()
if (isRoundDevice()) {
CurvedLayout(modifier = modifier) {
@@ -122,7 +127,8 @@
modifier =
curvedModifier
.sizeIn(maxSweepDegrees = maxSweepAngle)
- .padding(contentPadding.toArcPadding()),
+ .padding(contentPadding.toArcPadding())
+ .background(backgroundColor, StrokeCap.Round),
radialAlignment = CurvedAlignment.Radial.Center
) {
CurvedTimeTextScope(timeText, timeTextStyle, maxSweepAngle, contentColor).apply {
@@ -134,7 +140,10 @@
} else {
Box(modifier.fillMaxSize()) {
Row(
- modifier = Modifier.align(Alignment.TopCenter).padding(contentPadding),
+ modifier =
+ Modifier.align(Alignment.TopCenter)
+ .background(backgroundColor, CircleShape)
+ .padding(contentPadding),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleButton.kt
deleted file mode 100644
index cc9cc49..0000000
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleButton.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright 2024 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.wear.compose.material3
-
-import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.tween
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.State
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.takeOrElse
-import androidx.wear.compose.material3.tokens.MotionTokens
-import androidx.wear.compose.materialcore.animateSelectionColor
-
-/**
- * Represents the different container and content colors used for [IconToggleButton] and
- * [TextToggleButton]) in various states, that are checked, unchecked, enabled and disabled.
- *
- * @param checkedContainerColor Container or background color when the toggle button is checked
- * @param checkedContentColor Color of the content (text or icon) when the toggle button is checked
- * @param uncheckedContainerColor Container or background color when the toggle button is unchecked
- * @param uncheckedContentColor Color of the content (text or icon) when the toggle button is
- * unchecked
- * @param disabledCheckedContainerColor Container or background color when the toggle button is
- * disabled and checked
- * @param disabledCheckedContentColor Color of content (text or icon) when the toggle button is
- * disabled and checked
- * @param disabledUncheckedContainerColor Container or background color when the toggle button is
- * disabled and unchecked
- * @param disabledUncheckedContentColor Color of the content (text or icon) when the toggle button
- * is disabled and unchecked
- */
-@Immutable
-class ToggleButtonColors(
- val checkedContainerColor: Color,
- val checkedContentColor: Color,
- val uncheckedContainerColor: Color,
- val uncheckedContentColor: Color,
- val disabledCheckedContainerColor: Color,
- val disabledCheckedContentColor: Color,
- val disabledUncheckedContainerColor: Color,
- val disabledUncheckedContentColor: Color,
-) {
- internal fun copy(
- checkedContainerColor: Color,
- checkedContentColor: Color,
- uncheckedContainerColor: Color,
- uncheckedContentColor: Color,
- disabledCheckedContainerColor: Color,
- disabledCheckedContentColor: Color,
- disabledUncheckedContainerColor: Color,
- disabledUncheckedContentColor: Color,
- ): ToggleButtonColors =
- ToggleButtonColors(
- checkedContainerColor = checkedContainerColor.takeOrElse { this.checkedContainerColor },
- checkedContentColor = checkedContentColor.takeOrElse { this.checkedContentColor },
- uncheckedContainerColor =
- uncheckedContainerColor.takeOrElse { this.uncheckedContainerColor },
- uncheckedContentColor = uncheckedContentColor.takeOrElse { this.uncheckedContentColor },
- disabledCheckedContainerColor =
- disabledCheckedContainerColor.takeOrElse { this.disabledCheckedContainerColor },
- disabledCheckedContentColor =
- disabledCheckedContentColor.takeOrElse { this.disabledCheckedContentColor },
- disabledUncheckedContainerColor =
- disabledUncheckedContainerColor.takeOrElse { this.disabledUncheckedContainerColor },
- disabledUncheckedContentColor =
- disabledUncheckedContentColor.takeOrElse { this.disabledUncheckedContentColor },
- )
-
- /**
- * Determines the container color based on whether the toggle button is [enabled] and [checked].
- *
- * @param enabled Whether the toggle button is enabled
- * @param checked Whether the toggle button is checked
- */
- @Composable
- internal fun containerColor(enabled: Boolean, checked: Boolean): State<Color> =
- animateSelectionColor(
- enabled = enabled,
- checked = checked,
- checkedColor = checkedContainerColor,
- uncheckedColor = uncheckedContainerColor,
- disabledCheckedColor = disabledCheckedContainerColor,
- disabledUncheckedColor = disabledUncheckedContainerColor,
- animationSpec = COLOR_ANIMATION_SPEC
- )
-
- /**
- * Determines the content color based on whether the toggle button is [enabled] and [checked].
- *
- * @param enabled Whether the toggle button is enabled
- * @param checked Whether the toggle button is checked
- */
- @Composable
- internal fun contentColor(enabled: Boolean, checked: Boolean): State<Color> =
- animateSelectionColor(
- enabled = enabled,
- checked = checked,
- checkedColor = checkedContentColor,
- uncheckedColor = uncheckedContentColor,
- disabledCheckedColor = disabledCheckedContentColor,
- disabledUncheckedColor = disabledUncheckedContentColor,
- animationSpec = COLOR_ANIMATION_SPEC
- )
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null) return false
- if (this::class != other::class) return false
-
- other as ToggleButtonColors
-
- if (checkedContainerColor != other.checkedContainerColor) return false
- if (checkedContentColor != other.checkedContentColor) return false
- if (uncheckedContainerColor != other.uncheckedContainerColor) return false
- if (uncheckedContentColor != other.uncheckedContentColor) return false
- if (disabledCheckedContainerColor != other.disabledCheckedContainerColor) return false
- if (disabledCheckedContentColor != other.disabledCheckedContentColor) return false
- if (disabledUncheckedContainerColor != other.disabledUncheckedContainerColor) return false
- if (disabledUncheckedContentColor != other.disabledUncheckedContentColor) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = checkedContainerColor.hashCode()
- result = 31 * result + checkedContentColor.hashCode()
- result = 31 * result + uncheckedContainerColor.hashCode()
- result = 31 * result + uncheckedContentColor.hashCode()
- result = 31 * result + disabledCheckedContainerColor.hashCode()
- result = 31 * result + disabledCheckedContentColor.hashCode()
- result = 31 * result + disabledUncheckedContainerColor.hashCode()
- result = 31 * result + disabledUncheckedContentColor.hashCode()
- return result
- }
-}
-
-private val COLOR_ANIMATION_SPEC: AnimationSpec<Color> =
- tween(MotionTokens.DurationMedium1, 0, MotionTokens.EasingStandardDecelerate)
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
index 4edf6fd..478d030 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
@@ -65,8 +65,20 @@
inline val TimePickerPeriod
get() = Strings(R.string.wear_m3c_time_picker_period)
+ inline val DatePickerYear
+ get() = Strings(R.string.wear_m3c_date_picker_year)
+
+ inline val DatePickerMonth
+ get() = Strings(R.string.wear_m3c_date_picker_month)
+
+ inline val DatePickerDay
+ get() = Strings(R.string.wear_m3c_date_picker_day)
+
inline val PickerConfirmButtonContentDescription
get() = Strings(R.string.wear_m3c_picker_confirm_button_content_description)
+
+ inline val PickerNextButtonContentDescription
+ get() = Strings(R.string.wear_m3c_picker_next_button_content_description)
}
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/DatePickerTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/DatePickerTokens.kt
new file mode 100644
index 0000000..9c53ae8
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/DatePickerTokens.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 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.wear.compose.material3.tokens
+
+internal object DatePickerTokens {
+ val SelectedPickerContentColor = ColorSchemeKeyTokens.OnBackground
+ val UnselectedPickerContentColor = ColorSchemeKeyTokens.SecondaryDim
+ val PickerLabelColor = ColorSchemeKeyTokens.Primary
+ val NextButtonContentColor = ColorSchemeKeyTokens.Primary
+ val NextButtonContainerColor = ColorSchemeKeyTokens.SurfaceContainer
+ val ConfirmButtonContentColor = ColorSchemeKeyTokens.OnPrimary
+ val ConfirmButtonContainerColor = ColorSchemeKeyTokens.PrimaryDim
+
+ val PickerLabelLargeTypography = TypographyKeyTokens.TitleLarge
+ val PickerLabelTypography = TypographyKeyTokens.TitleMedium
+ val PickerContentLargeTypography = TypographyKeyTokens.NumeralMedium
+ val PickerContentTypography = TypographyKeyTokens.NumeralSmall
+}
diff --git a/wear/compose/compose-material3/src/main/res/drawable/check_animation.xml b/wear/compose/compose-material3/src/main/res/drawable/check_animation.xml
new file mode 100644
index 0000000..90fd02a
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/drawable/check_animation.xml
@@ -0,0 +1,17 @@
+<!--
+ Copyright 2024 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.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="300dp" android:width="400dp" android:viewportHeight="300" android:viewportWidth="400"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="200" android:translateY="176" android:scaleX="1.05507" android:scaleY="1.05556"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="34" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M-147.5 -20 C-147.5,-20 -51.5,76 -51.5,76 C-51.5,76 151.5,-127 151.5,-127 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="10017" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/wear/compose/compose-material3/src/main/res/drawable/failure_animation.xml b/wear/compose/compose-material3/src/main/res/drawable/failure_animation.xml
new file mode 100644
index 0000000..2c5120b
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/drawable/failure_animation.xml
@@ -0,0 +1,17 @@
+<!--
+ Copyright 2024 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.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="800dp" android:width="800dp" android:viewportHeight="800" android:viewportWidth="800"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="414" android:translateY="398" android:scaleX="10.57" android:scaleY="10.57"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#ff8986" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-14.49 17.77 C-14.49,17.77 -14.49,19.61 -14.49,19.61 C-14.49,19.72 -14.45,19.81 -14.38,19.88 C-14.31,19.95 -14.22,19.99 -14.11,19.99 C-14.11,19.99 7.78,19.99 7.78,19.99 C7.88,19.99 7.97,19.95 8.05,19.88 C8.12,19.81 8.15,19.72 8.15,19.61 C8.15,19.61 8.15,17.77 8.15,17.77 C8.15,17.77 -14.49,17.77 -14.49,17.77c M-14.49 -17.77 C-14.49,-17.77 8.15,-17.77 8.15,-17.77 C8.15,-17.77 8.15,-19.61 8.15,-19.61 C8.15,-19.72 8.12,-19.81 8.05,-19.88 C7.97,-19.95 7.88,-19.99 7.78,-19.99 C7.78,-19.99 -14.11,-19.99 -14.11,-19.99 C-14.22,-19.99 -14.31,-19.95 -14.38,-19.88 C-14.45,-19.81 -14.49,-19.72 -14.49,-19.61 C-14.49,-19.61 -14.49,-17.77 -14.49,-17.77c M-14.11 23.29 C-15.12,23.29 -15.99,22.93 -16.71,22.21 C-17.43,21.49 -17.79,20.62 -17.79,19.61 C-17.79,19.61 -17.79,-19.61 -17.79,-19.61 C-17.79,-20.62 -17.43,-21.49 -16.71,-22.21 C-15.99,-22.93 -15.12,-23.29 -14.11,-23.29 C-14.11,-23.29 7.78,-23.29 7.78,-23.29 C8.79,-23.29 9.65,-22.93 10.38,-22.21 C11.1,-21.49 11.46,-20.62 11.46,-19.61 C11.46,-19.61 11.46,-13.32 11.46,-13.32 C11.46,-12.86 11.3,-12.46 10.97,-12.13 C10.65,-11.81 10.25,-11.65 9.78,-11.65 C9.31,-11.65 8.91,-11.81 8.59,-12.13 C8.3,-12.46 8.15,-12.86 8.15,-13.32 C8.15,-13.32 8.15,-14.46 8.15,-14.46 C8.15,-14.46 -14.49,-14.46 -14.49,-14.46 C-14.49,-14.46 -14.49,14.46 -14.49,14.46 C-14.49,14.46 8.15,14.46 8.15,14.46 C8.15,14.46 8.15,13.33 8.15,13.33 C8.15,12.86 8.3,12.46 8.59,12.13 C8.91,11.81 9.31,11.65 9.78,11.65 C10.25,11.65 10.65,11.81 10.97,12.13 C11.3,12.46 11.46,12.86 11.46,13.33 C11.46,13.33 11.46,19.61 11.46,19.61 C11.46,20.62 11.1,21.49 10.38,22.21 C9.65,22.93 8.79,23.29 7.78,23.29 C7.78,23.29 -14.11,23.29 -14.11,23.29c "/></group><group android:name="_R_G_L_1_G" android:translateX="400" android:translateY="400"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#003352" android:fillAlpha="1" android:fillType="nonZero" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M52 61 C52,61 179,-67 179,-67 "/><path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#ff8986" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="34" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M52 61 C52,61 179,-67 179,-67 "/></group><group android:name="_R_G_L_0_G" android:translateX="400" android:translateY="400"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#003352" android:fillAlpha="1" android:fillType="nonZero" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M52 -66 C52,-66 177,60 177,60 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#ff8986" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="35" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M52 -66 C52,-66 177,60 177,60 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="100" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="267" android:startOffset="100" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="100" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="267" android:startOffset="100" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="267" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="267" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="10017" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/wear/compose/compose-material3/src/main/res/drawable/open_on_phone_animation.xml b/wear/compose/compose-material3/src/main/res/drawable/open_on_phone_animation.xml
new file mode 100644
index 0000000..26c7f42
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/drawable/open_on_phone_animation.xml
@@ -0,0 +1,17 @@
+<!--
+ Copyright 2024 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.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="800dp" android:width="800dp" android:viewportHeight="800" android:viewportWidth="800"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="372" android:translateY="396" android:scaleX="9.86" android:scaleY="9.86"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-7.44 23.29 C-8.45,23.29 -9.32,22.93 -10.04,22.21 C-10.76,21.49 -11.13,20.62 -11.13,19.61 C-11.13,19.61 -11.13,8.18 -11.13,8.18 C-11.13,7.71 -10.96,7.31 -10.64,6.99 C-10.31,6.66 -9.91,6.5 -9.45,6.5 C-8.98,6.5 -8.6,6.66 -8.31,6.99 C-7.98,7.31 -7.82,7.71 -7.82,8.18 C-7.82,8.18 -7.82,14.46 -7.82,14.46 C-7.82,14.46 14.82,14.46 14.82,14.46 C14.82,14.46 14.82,-14.46 14.82,-14.46 C14.82,-14.46 -7.82,-14.46 -7.82,-14.46 C-7.82,-14.46 -7.82,-8.18 -7.82,-8.18 C-7.82,-7.71 -7.98,-7.31 -8.31,-6.99 C-8.6,-6.66 -8.98,-6.5 -9.45,-6.5 C-9.91,-6.5 -10.31,-6.66 -10.64,-6.99 C-10.96,-7.31 -11.13,-7.71 -11.13,-8.18 C-11.13,-8.18 -11.13,-19.61 -11.13,-19.61 C-11.13,-20.62 -10.76,-21.49 -10.04,-22.21 C-9.32,-22.93 -8.45,-23.29 -7.44,-23.29 C-7.44,-23.29 14.44,-23.29 14.44,-23.29 C15.45,-23.29 16.32,-22.93 17.04,-22.21 C17.76,-21.49 18.12,-20.62 18.12,-19.61 C18.12,-19.61 18.12,19.61 18.12,19.61 C18.12,20.62 17.76,21.49 17.04,22.21 C16.32,22.93 15.45,23.29 14.44,23.29 C14.44,23.29 -7.44,23.29 -7.44,23.29c M-7.82 17.77 C-7.82,17.77 -7.82,19.61 -7.82,19.61 C-7.82,19.72 -7.78,19.81 -7.71,19.88 C-7.64,19.95 -7.55,19.99 -7.44,19.99 C-7.44,19.99 14.44,19.99 14.44,19.99 C14.55,19.99 14.64,19.95 14.71,19.88 C14.78,19.81 14.82,19.72 14.82,19.61 C14.82,19.61 14.82,17.77 14.82,17.77 C14.82,17.77 -7.82,17.77 -7.82,17.77c M-7.82 -17.77 C-7.82,-17.77 14.82,-17.77 14.82,-17.77 C14.82,-17.77 14.82,-19.61 14.82,-19.61 C14.82,-19.72 14.78,-19.81 14.71,-19.88 C14.64,-19.95 14.55,-19.99 14.44,-19.99 C14.44,-19.99 -7.44,-19.99 -7.44,-19.99 C-7.55,-19.99 -7.64,-19.95 -7.71,-19.88 C-7.78,-19.81 -7.82,-19.72 -7.82,-19.61 C-7.82,-19.61 -7.82,-17.77 -7.82,-17.77c "/></group><group android:name="_R_G_L_1_G" android:translateX="370" android:translateY="428" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#003352" android:fillAlpha="1" android:fillType="nonZero" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M-189 -33 C-189,-33 20,-33 20,-33 "/><path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="30" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M-189 -33 C-189,-33 20,-33 20,-33 "/></group><group android:name="_R_G_L_0_G_T_1" android:translateX="180" android:translateY="395" android:scaleY="0"><group android:name="_R_G_L_0_G" android:translateX="-20" android:translateY="33"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="32" android:strokeAlpha="1" android:trimPathStart="0.49" android:trimPathEnd="0.51" android:trimPathOffset="0" android:pathData=" M-36 -90 C-36,-90 21,-33 21,-33 C21,-33 -36,24 -36,24 "/></group></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="217" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="217" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateXY" android:duration="67" android:startOffset="0" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 370,428C 370,428 370,428 370,428"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateXY" android:duration="217" android:startOffset="67" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 370,428C 370,428 412,428 412,428"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateXY" android:duration="200" android:startOffset="283" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 412,428C 412,428 400,428 400,428"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.44,0 0.55,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="133" android:startOffset="0" android:valueFrom="0.49" android:valueTo="0.49" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathStart" android:duration="100" android:startOffset="133" android:valueFrom="0.49" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="133" android:startOffset="0" android:valueFrom="0.51" android:valueTo="0.51" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="100" android:startOffset="133" android:valueFrom="0.51" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_T_1"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateXY" android:duration="67" android:startOffset="0" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 180,395C 180,395 180,395 180,395"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateXY" android:duration="217" android:startOffset="67" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 180,395C 180,395 432,395 432,395"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.5,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="translateXY" android:duration="200" android:startOffset="283" android:propertyXName="translateX" android:propertyYName="translateY" android:pathData="M 432,395C 432,395 420,395 420,395"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.44,0 0.55,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_T_1"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="10017" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/wear/compose/compose-material3/src/main/res/values-af/strings.xml b/wear/compose/compose-material3/src/main/res/values-af/strings.xml
new file mode 100644
index 0000000..4951307
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-af/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Uur"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minute"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekonde"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d uur</item>
+ <item quantity="one">%d uur</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minute</item>
+ <item quantity="one">%d minuut</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d sekondes</item>
+ <item quantity="one">%d sekonde</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Tydperk"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bevestig"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-am/strings.xml b/wear/compose/compose-material3/src/main/res/values-am/strings.xml
new file mode 100644
index 0000000..da5ab9c
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-am/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"ሰዓት"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"ደቂቃ"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"ሰከንድ"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d ሰዓት</item>
+ <item quantity="other">%d ሰዓታት</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d ደቂቃ</item>
+ <item quantity="other">%d ደቂቃዎች</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d ሰከንድ</item>
+ <item quantity="other">%d ሰከንዶች</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"ክፍለ ጊዜ"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"አረጋግጥ"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ar/strings.xml b/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..450227f
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"ساعة"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"دقيقة"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"ثانية"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="zero">%d ساعة</item>
+ <item quantity="two">ساعتان</item>
+ <item quantity="few">%d ساعات</item>
+ <item quantity="many">%d ساعة</item>
+ <item quantity="other">%d ساعة</item>
+ <item quantity="one">ساعة واحدة</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="zero">%d دقيقة</item>
+ <item quantity="two">دقيقتان</item>
+ <item quantity="few">%d دقائق</item>
+ <item quantity="many">%d دقيقة</item>
+ <item quantity="other">%d دقيقة</item>
+ <item quantity="one">دقيقة واحدة</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="zero">%d ثانية</item>
+ <item quantity="two">ثانيتان</item>
+ <item quantity="few">%d ثوانٍ</item>
+ <item quantity="many">%d ثانية</item>
+ <item quantity="other">%d ثانية</item>
+ <item quantity="one">ثانية واحدة</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"فترة"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"تأكيد"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-as/strings.xml b/wear/compose/compose-material3/src/main/res/values-as/strings.xml
new file mode 100644
index 0000000..178eaa4
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-as/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"ঘণ্টা"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"মিনিট"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"দ্বিতীয়"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d ঘণ্টা</item>
+ <item quantity="other">%d ঘণ্টা</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d মিনিট</item>
+ <item quantity="other">%d মিনিট</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d ছেকেণ্ড</item>
+ <item quantity="other">%d ছেকেণ্ড</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"পিৰিয়ড"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"নিশ্চিত কৰক"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-az/strings.xml b/wear/compose/compose-material3/src/main/res/values-az/strings.xml
new file mode 100644
index 0000000..de43d4f
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-az/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Saat"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Dəqiqə"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Saniyə"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d saat</item>
+ <item quantity="one">%d saat</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d dəqiqə</item>
+ <item quantity="one">%d dəqiqə</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d saniyə</item>
+ <item quantity="one">%d saniyə</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Müddət"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Təsdiq edin"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml b/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..eca919a
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Sat"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minut"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekunda"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d sat</item>
+ <item quantity="few">%d sata</item>
+ <item quantity="other">%d sati</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d minut</item>
+ <item quantity="few">%d minuta</item>
+ <item quantity="other">%d minuta</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d sekunda</item>
+ <item quantity="few">%d sekunde</item>
+ <item quantity="other">%d sekundi</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Period"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdi"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-be/strings.xml b/wear/compose/compose-material3/src/main/res/values-be/strings.xml
new file mode 100644
index 0000000..dc5e832
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-be/strings.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Гадзіны"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Хвіліны"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Секунды"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d гадзіна</item>
+ <item quantity="few">%d гадзіны</item>
+ <item quantity="many">%d гадзін</item>
+ <item quantity="other">%d гадзіны</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d хвіліна</item>
+ <item quantity="few">%d хвіліны</item>
+ <item quantity="many">%d хвілін</item>
+ <item quantity="other">%d хвіліны</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d секунда</item>
+ <item quantity="few">%d секунды</item>
+ <item quantity="many">%d секунд</item>
+ <item quantity="other">%d секунды</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Перыяд"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Пацвердзіць"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-bg/strings.xml b/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000..2866f08
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Час"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Минута"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Секунда"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d часа</item>
+ <item quantity="one">%d час</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d минути</item>
+ <item quantity="one">%d минута</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d секунди</item>
+ <item quantity="one">%d секунда</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Точка"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"Ден"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"Месец"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Година"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Потвърждаване"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Напред"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-bn/strings.xml b/wear/compose/compose-material3/src/main/res/values-bn/strings.xml
new file mode 100644
index 0000000..34a09f1
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-bn/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"ঘণ্টা"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"মিনিট"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"সেকেন্ড"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d ঘণ্টা</item>
+ <item quantity="other">%d ঘণ্টা</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d মিনিট</item>
+ <item quantity="other">%d মিনিট</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d সেকেন্ড</item>
+ <item quantity="other">%d সেকেন্ড</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"সময়সীমা"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"কনফার্ম করুন"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-bs/strings.xml b/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
new file mode 100644
index 0000000..9e53fc8
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Sat"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuta"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekunda"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d sat</item>
+ <item quantity="few">%d sata</item>
+ <item quantity="other">%d sati</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d minuta</item>
+ <item quantity="few">%d minute</item>
+ <item quantity="other">%d minuta</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d sekunda</item>
+ <item quantity="few">%d sekunde</item>
+ <item quantity="other">%d sekundi</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Period"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrđivanje"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ca/strings.xml b/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000..0c19945
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hora"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minut"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Segon"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="many">%d d\'hores</item>
+ <item quantity="other">%d hores</item>
+ <item quantity="one">%d hora</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="many">%d de minuts</item>
+ <item quantity="other">%d minuts</item>
+ <item quantity="one">%d minut</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="many">%d de segons</item>
+ <item quantity="other">%d segons</item>
+ <item quantity="one">%d segon</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Període"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"Dia"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"Mes"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Any"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirma"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Següent"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-cs/strings.xml b/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000..98dc3ea
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hodiny"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuty"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekundy"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="few">%1$d hodiny</item>
+ <item quantity="many">%1$d hodiny</item>
+ <item quantity="other">%1$d hodin</item>
+ <item quantity="one">%d hodina</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="few">%1$d minuty</item>
+ <item quantity="many">%1$d minuty</item>
+ <item quantity="other">%1$d minut</item>
+ <item quantity="one">%d minuta</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="few">%d sekundy</item>
+ <item quantity="many">%d sekundy</item>
+ <item quantity="other">%d sekund</item>
+ <item quantity="one">%d sekunda</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Období"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdit"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-da/strings.xml b/wear/compose/compose-material3/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000..d1bd436
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-da/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Time"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minut"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekund"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d time</item>
+ <item quantity="other">%d timer</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d minut</item>
+ <item quantity="other">%d minutter</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d sekund</item>
+ <item quantity="other">%d sekunder</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Format"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bekræft"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-de/strings.xml b/wear/compose/compose-material3/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..9fdd438
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-de/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Stunde"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minute"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekunde"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d Stunden</item>
+ <item quantity="one">%d Stunde</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d Minuten</item>
+ <item quantity="one">%d Minute</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d Sekunden</item>
+ <item quantity="one">%d Sekunde</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Zeitraum"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bestätigen"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-el/strings.xml b/wear/compose/compose-material3/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000..20374c9
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-el/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Ώρα"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Λεπτό"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Δευτ."</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d ώρες</item>
+ <item quantity="one">%d ώρα</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d λεπτά</item>
+ <item quantity="one">%d λεπτό</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d δευτερόλεπτα</item>
+ <item quantity="one">%d δευτερόλεπτο</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Περίοδος"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Επιβεβαίωση"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..58982b2
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hour"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minute"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Second"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d hours</item>
+ <item quantity="one">%d hour</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minutes</item>
+ <item quantity="one">%d minute</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d seconds</item>
+ <item quantity="one">%d second</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Period"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirm"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rCA/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..fb5c42c
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-en-rCA/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hour"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minute"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Second"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d Hours</item>
+ <item quantity="one">%d Hour</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d Minutes</item>
+ <item quantity="one">%d Minute</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d Seconds</item>
+ <item quantity="one">%d Second</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Period"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"Day"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"Month"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Year"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirm"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Next"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..58982b2
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hour"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minute"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Second"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d hours</item>
+ <item quantity="one">%d hour</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minutes</item>
+ <item quantity="one">%d minute</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d seconds</item>
+ <item quantity="one">%d second</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Period"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirm"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..58982b2
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hour"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minute"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Second"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d hours</item>
+ <item quantity="one">%d hour</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minutes</item>
+ <item quantity="one">%d minute</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d seconds</item>
+ <item quantity="one">%d second</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Period"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirm"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rXC/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..0bff2a4
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-en-rXC/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hour"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minute"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Second"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d Hours</item>
+ <item quantity="one">%d Hour</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d Minutes</item>
+ <item quantity="one">%d Minute</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d Seconds</item>
+ <item quantity="one">%d Second</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Period"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"Day"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"Month"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Year"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirm"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Next"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-es-rUS/strings.xml b/wear/compose/compose-material3/src/main/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..5dac2b5
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-es-rUS/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hora"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuto"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Segundo"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="many">%d de horas</item>
+ <item quantity="other">%d horas</item>
+ <item quantity="one">%d hora</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="many">%d de minutos</item>
+ <item quantity="other">%d minutos</item>
+ <item quantity="one">%d minuto</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="many">%d de segundos</item>
+ <item quantity="other">%d segundos</item>
+ <item quantity="one">%d segundo</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Período"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-es/strings.xml b/wear/compose/compose-material3/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..7ed6eed
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-es/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hora"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuto"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Segundo"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="many">%d horas</item>
+ <item quantity="other">%d horas</item>
+ <item quantity="one">%d hora</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="many">%d minutos</item>
+ <item quantity="other">%d minutos</item>
+ <item quantity="one">%d minuto</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="many">%d segundos</item>
+ <item quantity="other">%d segundos</item>
+ <item quantity="one">%d segundo</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Periodo"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-et/strings.xml b/wear/compose/compose-material3/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000..898d4f1
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-et/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Tunnid"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minutid"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekund"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d tundi</item>
+ <item quantity="one">%d tund</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minutit</item>
+ <item quantity="one">%d minut</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d sekundit</item>
+ <item quantity="one">%d sekund</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Periood"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Kinnita"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-eu/strings.xml b/wear/compose/compose-material3/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000..202e7ee
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-eu/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Ordua"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minutua"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Segundoa"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d ordu</item>
+ <item quantity="one">%d ordu</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minutu</item>
+ <item quantity="one">%d minutu</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d segundo</item>
+ <item quantity="one">%d segundo</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Epea"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Berretsi"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-fa/strings.xml b/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000..8f9c137
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"ساعت"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"دقیقه"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"ثانیه"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d ساعت</item>
+ <item quantity="other">%d ساعت</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d دقیقه</item>
+ <item quantity="other">%d دقیقه</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d ثانیه</item>
+ <item quantity="other">%d ثانیه</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"مدت زمان"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"تأیید کردن"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-fi/strings.xml b/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000..0f0b50b
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Tunti"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuutti"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Toinen"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d tuntia</item>
+ <item quantity="one">%d tunti</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minuuttia</item>
+ <item quantity="one">%d minuutti</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d sekuntia</item>
+ <item quantity="one">%d sekunti</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Jakso"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Vahvista"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml b/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..cdc3ad5
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Heure"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minute"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Seconde"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d heure</item>
+ <item quantity="many">%d d\'heures</item>
+ <item quantity="other">%d heures</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d minute</item>
+ <item quantity="many">%d de minutes</item>
+ <item quantity="other">%d minutes</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d seconde</item>
+ <item quantity="many">%d de secondes</item>
+ <item quantity="other">%d secondes</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Période"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmer"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-fr/strings.xml b/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..bdb1f1b
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Heure"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minute"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Seconde"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d heure</item>
+ <item quantity="many">%d d\'heures</item>
+ <item quantity="other">%d heures</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d minute</item>
+ <item quantity="many">%d de minutes</item>
+ <item quantity="other">%d minutes</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d seconde</item>
+ <item quantity="many">%d de secondes</item>
+ <item quantity="other">%d secondes</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Période"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmer"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-gl/strings.xml b/wear/compose/compose-material3/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000..ad5ca33
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-gl/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hora"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuto"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Segundo"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d horas</item>
+ <item quantity="one">%d hora</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minutos</item>
+ <item quantity="one">%d minuto</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d segundos</item>
+ <item quantity="one">%d segundo</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Período"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-gu/strings.xml b/wear/compose/compose-material3/src/main/res/values-gu/strings.xml
new file mode 100644
index 0000000..b01e9be
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-gu/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"કલાક"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"મિનિટ"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"સેકન્ડ"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d કલાક</item>
+ <item quantity="other">%d કલાક</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d મિનિટ</item>
+ <item quantity="other">%d મિનિટ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d સેકન્ડ</item>
+ <item quantity="other">%d સેકન્ડ</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"અવધિ"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"કન્ફર્મ કરો"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-hi/strings.xml b/wear/compose/compose-material3/src/main/res/values-hi/strings.xml
new file mode 100644
index 0000000..c861b3af
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-hi/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"घंटा"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"मिनट"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"दूसरा"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d घंटा</item>
+ <item quantity="other">%d घंटे</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d मिनट</item>
+ <item quantity="other">%d मिनट</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d सेकंड</item>
+ <item quantity="other">%d सेकंड</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"समयअवधि"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"दिन"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"महीना"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"साल"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"पुष्टि करें"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"अगला"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-hr/strings.xml b/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000..af212a5
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Sat"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuta"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekunda"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d sat</item>
+ <item quantity="few">%d sata</item>
+ <item quantity="other">%d sati</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d minuta</item>
+ <item quantity="few">%d minute</item>
+ <item quantity="other">%d minuta</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d sekunda</item>
+ <item quantity="few">%d sekunde</item>
+ <item quantity="other">%d sekundi</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Razdoblje"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdi"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-hu/strings.xml b/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000..2cce16e
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Óra"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Perc"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Mp."</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d óra</item>
+ <item quantity="one">%d óra</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d perc</item>
+ <item quantity="one">%d perc</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d másodperc</item>
+ <item quantity="one">%d másodperc</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Időszak"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Megerősítés"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-hy/strings.xml b/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
new file mode 100644
index 0000000..cda99db
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Ժամ"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Րոպե"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Վայրկյան"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d ժամ</item>
+ <item quantity="other">%d ժամ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d րոպե</item>
+ <item quantity="other">%d րոպե</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d վայրկյան</item>
+ <item quantity="other">%d վայրկյան</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Ժամանակահատված"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Հաստատել"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-in/strings.xml b/wear/compose/compose-material3/src/main/res/values-in/strings.xml
new file mode 100644
index 0000000..66a973e
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-in/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Jam"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Menit"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Detik"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d Jam</item>
+ <item quantity="one">%d Jam</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d Menit</item>
+ <item quantity="one">%d Menit</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d Detik</item>
+ <item quantity="one">%d Detik</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Jangka waktu"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Konfirmasi"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-is/strings.xml b/wear/compose/compose-material3/src/main/res/values-is/strings.xml
new file mode 100644
index 0000000..8983c3f
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-is/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Klukkustund"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Mínúta"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekúnda"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d klukkustund</item>
+ <item quantity="other">%d klukkustundir</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d mínúta</item>
+ <item quantity="other">%d mínútur</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d sekúnda</item>
+ <item quantity="other">%d sekúndur</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Punktur"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Staðfesta"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-it/strings.xml b/wear/compose/compose-material3/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..a9566b7
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-it/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Ora"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuto"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Secondo"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="many">%d di ore</item>
+ <item quantity="other">%d ore</item>
+ <item quantity="one">%d ora</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="many">%d di minuti</item>
+ <item quantity="other">%d minuti</item>
+ <item quantity="one">%d minuto</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="many">%d di secondi</item>
+ <item quantity="other">%d secondi</item>
+ <item quantity="one">%d secondo</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Periodo"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Conferma"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-iw/strings.xml b/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
new file mode 100644
index 0000000..91f1e27
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"שעות"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"דקות"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"שניות"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d שעות</item>
+ <item quantity="two">שעתיים (%d)</item>
+ <item quantity="other">%d שעות</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d דקות</item>
+ <item quantity="two">%d דקות</item>
+ <item quantity="other">%d דקות</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d שניות</item>
+ <item quantity="two">%d שניות</item>
+ <item quantity="other">%d שניות</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"תקופת זמן"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"אישור"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ja/strings.xml b/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000..65e9cc7
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"時間"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"分"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"秒"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d 時間</item>
+ <item quantity="one">%d 時間</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d 分</item>
+ <item quantity="one">%d 分</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d 秒</item>
+ <item quantity="one">%d 秒</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"期間"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"日"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"月"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"年"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"確認"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"次へ"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ka/strings.xml b/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
new file mode 100644
index 0000000..4f4734e
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"საათი"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"წუთი"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"წამი"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d საათი</item>
+ <item quantity="one">%d საათი</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d წუთი</item>
+ <item quantity="one">%d წუთი</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d წამი</item>
+ <item quantity="one">%d წამი</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"პერიოდი"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"დღე"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"თვე"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"წელი"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"დადასტურება"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"შემდეგი"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-kk/strings.xml b/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
new file mode 100644
index 0000000..e0d8b3d
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Сағат"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Mинут"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Секунд"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d сағат</item>
+ <item quantity="one">%d сағат</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d минут</item>
+ <item quantity="one">%d минут</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d секунд</item>
+ <item quantity="one">%d секунд</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Кезең"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Растау"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-km/strings.xml b/wear/compose/compose-material3/src/main/res/values-km/strings.xml
new file mode 100644
index 0000000..410a6f2
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-km/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"ម៉ោង"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"នាទី"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"វិនាទី"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d ម៉ោង</item>
+ <item quantity="one">%d ម៉ោង</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d នាទី</item>
+ <item quantity="one">%d នាទី</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d វិនាទី</item>
+ <item quantity="one">%d វិនាទី</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"រយៈពេល"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"បញ្ជាក់"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-kn/strings.xml b/wear/compose/compose-material3/src/main/res/values-kn/strings.xml
new file mode 100644
index 0000000..6ae6994
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-kn/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"ಗಂಟೆ"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"ನಿಮಿಷ"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"ಸೆಕೆಂಡ್"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d ಗಂಟೆಗಳು</item>
+ <item quantity="other">%d ಗಂಟೆಗಳು</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d ನಿಮಿಷಗಳು</item>
+ <item quantity="other">%d ನಿಮಿಷಗಳು</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d ಸೆಕೆಂಡ್ಗಳು</item>
+ <item quantity="other">%d ಸೆಕೆಂಡ್ಗಳು</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"ಅವಧಿ"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"ದೃಢೀಕರಿಸಿ"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ko/strings.xml b/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000..45f8446
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"시간"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"분"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"초"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d시간</item>
+ <item quantity="one">%d시간</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d분</item>
+ <item quantity="one">%d분</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d초</item>
+ <item quantity="one">%d초</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"기간"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"확인"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ky/strings.xml b/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
new file mode 100644
index 0000000..692726e
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Саат"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Мүнөт"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Секунд"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d саат</item>
+ <item quantity="one">%d саат</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d мүнөт</item>
+ <item quantity="one">%d мүнөт</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d секунд</item>
+ <item quantity="one">%d секунд</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Чекит"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Ырастоо"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-lo/strings.xml b/wear/compose/compose-material3/src/main/res/values-lo/strings.xml
new file mode 100644
index 0000000..59d2d94
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-lo/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"ຊົ່ວໂມງ"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"ນາທີ"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"ວິນາທີ"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d ຊົ່ວໂມງ</item>
+ <item quantity="one">%d ຊົ່ວໂມງ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d ນາທີ</item>
+ <item quantity="one">%d ນາທີ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d ວິນາທີ</item>
+ <item quantity="one">%d ວິນາທີ</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"ໄລຍະເວລາ"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"ຢືນຢັນ"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-lt/strings.xml b/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
new file mode 100644
index 0000000..615054e
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Valanda"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minutė"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekundė"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d valanda</item>
+ <item quantity="few">%d valandos</item>
+ <item quantity="many">%d valandos</item>
+ <item quantity="other">%d valandų</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d minutė</item>
+ <item quantity="few">%d minutės</item>
+ <item quantity="many">%d minutės</item>
+ <item quantity="other">%d minučių</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d sekundė</item>
+ <item quantity="few">%d sekundės</item>
+ <item quantity="many">%d sekundės</item>
+ <item quantity="other">%d sekundžių</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Laikotarpis"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Patvirtinti"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-lv/strings.xml b/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
new file mode 100644
index 0000000..08f2d2c
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Stunda"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minūte"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekunde"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="zero">%d stundu</item>
+ <item quantity="one">%d stunda</item>
+ <item quantity="other">%d stundas</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="zero">%d minūšu</item>
+ <item quantity="one">%d minūte</item>
+ <item quantity="other">%d minūtes</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="zero">%d sekunžu</item>
+ <item quantity="one">%d sekunde</item>
+ <item quantity="other">%d sekundes</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Periods"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Apstiprināt"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-mk/strings.xml b/wear/compose/compose-material3/src/main/res/values-mk/strings.xml
new file mode 100644
index 0000000..a093425
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-mk/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Час"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Минута"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Секунда"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d час</item>
+ <item quantity="other">%d часа</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d минута</item>
+ <item quantity="other">%d минути</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d секунда</item>
+ <item quantity="other">%d секунди</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Период"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Потврди"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ml/strings.xml b/wear/compose/compose-material3/src/main/res/values-ml/strings.xml
new file mode 100644
index 0000000..36d8b54
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ml/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"മണിക്കൂർ"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"മിനിറ്റ്"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"സെക്കൻഡ്"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d മണിക്കൂർ</item>
+ <item quantity="one">%d മണിക്കൂർ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d മിനിറ്റ്</item>
+ <item quantity="one">%d മിനിറ്റ്</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d സെക്കൻഡ്</item>
+ <item quantity="one">%d സെക്കൻഡ്</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"കാലയളവ്"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"സ്ഥിരീകരിക്കുക"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-mn/strings.xml b/wear/compose/compose-material3/src/main/res/values-mn/strings.xml
new file mode 100644
index 0000000..629e16a
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-mn/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Цаг"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Минут"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Секунд"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d цаг</item>
+ <item quantity="one">%d цаг</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d минут</item>
+ <item quantity="one">%d минут</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d секунд</item>
+ <item quantity="one">%d секунд</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Хугацаа"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Баталгаажуулах"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-mr/strings.xml b/wear/compose/compose-material3/src/main/res/values-mr/strings.xml
new file mode 100644
index 0000000..904ad4f
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-mr/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"तास"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"मिनिट"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"सेकंद"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d तास</item>
+ <item quantity="one">%d तास</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d मिनिटे</item>
+ <item quantity="one">%d मिनिट</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d सेकंद</item>
+ <item quantity="one">%d सेकंद</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"कालावधी"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"कन्फर्म करा"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ms/strings.xml b/wear/compose/compose-material3/src/main/res/values-ms/strings.xml
new file mode 100644
index 0000000..df4b8c9
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ms/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Jam"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minit"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Kedua"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d Jam</item>
+ <item quantity="one">%d Jam</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d Minit</item>
+ <item quantity="one">%d Minit</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d Saat</item>
+ <item quantity="one">%d Saat</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Tempoh"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Sahkan"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-my/strings.xml b/wear/compose/compose-material3/src/main/res/values-my/strings.xml
new file mode 100644
index 0000000..aa85494
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-my/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"နာရီ"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"မိနစ်"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"စက္ကန့်"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d နာရီ</item>
+ <item quantity="one">%d နာရီ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d မိနစ်</item>
+ <item quantity="one">%d မိနစ်</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d စက္ကန့်</item>
+ <item quantity="one">%d စက္ကန့်</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"အချိန်ကာလ"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"အတည်ပြုရန်"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-nb/strings.xml b/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
new file mode 100644
index 0000000..0d9dbd3
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Time"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minutt"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekund"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d timer</item>
+ <item quantity="one">%d time</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minutter</item>
+ <item quantity="one">%d minutt</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d sekunder</item>
+ <item quantity="one">%d sekund</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Periode"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bekreft"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ne/strings.xml b/wear/compose/compose-material3/src/main/res/values-ne/strings.xml
new file mode 100644
index 0000000..4f5d479
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ne/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"घण्टा"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"मिनेट"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"सेकेन्ड"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d घण्टा</item>
+ <item quantity="one">%d घण्टा</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d मिनेट</item>
+ <item quantity="one">%d मिनेट</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d सेकेन्ड</item>
+ <item quantity="one">%d सेकेन्ड</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"अवधि"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"पुष्टि गर्नुहोस्"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-nl/strings.xml b/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000..8561545
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Uur"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuut"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Seconde"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d uur</item>
+ <item quantity="one">%d uur</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minuten</item>
+ <item quantity="one">%d minuut</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d seconden</item>
+ <item quantity="one">%d seconde</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Periode"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bevestigen"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-or/strings.xml b/wear/compose/compose-material3/src/main/res/values-or/strings.xml
new file mode 100644
index 0000000..7701b598
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-or/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"ଘଣ୍ଟା"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"ମିନିଟ"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"ସେକେଣ୍ଡ"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d ଘଣ୍ଟା</item>
+ <item quantity="one">%d ଘଣ୍ଟା</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d ମିନିଟ</item>
+ <item quantity="one">%d ମିନିଟ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d ସେକେଣ୍ଡ</item>
+ <item quantity="one">%d ସେକେଣ୍ଡ</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"ଅବଧି"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"ସୁନିଶ୍ଚିତ କରନ୍ତୁ"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-pa/strings.xml b/wear/compose/compose-material3/src/main/res/values-pa/strings.xml
new file mode 100644
index 0000000..1000cde
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-pa/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"ਘੰਟੇ"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"ਮਿੰਟ"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"ਸਕਿੰਟ"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d ਘੰਟਾ</item>
+ <item quantity="other">%d ਘੰਟੇ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d ਮਿੰਟ</item>
+ <item quantity="other">%d ਮਿੰਟ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d ਸਕਿੰਟ</item>
+ <item quantity="other">%d ਸਕਿੰਟ</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"ਮਿਆਦ"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"ਤਸਦੀਕ ਕਰੋ"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-pl/strings.xml b/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000..5d56d82
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Godzina"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuta"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekunda"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="few">%d godziny</item>
+ <item quantity="many">%d godzin</item>
+ <item quantity="other">%d godziny</item>
+ <item quantity="one">%d godzina</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="few">%d minuty</item>
+ <item quantity="many">%d minut</item>
+ <item quantity="other">%d minuty</item>
+ <item quantity="one">%d minuta</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="few">%d sekundy</item>
+ <item quantity="many">%d sekund</item>
+ <item quantity="other">%d sekundy</item>
+ <item quantity="one">%d sekunda</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Kropka"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potwierdź"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..77c3710
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hora"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuto"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Segundo"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d hora</item>
+ <item quantity="many">%d de horas</item>
+ <item quantity="other">%d horas</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d minuto</item>
+ <item quantity="many">%d de minutos</item>
+ <item quantity="other">%d minutos</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d segundo</item>
+ <item quantity="many">%d de segundos</item>
+ <item quantity="other">%d segundos</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Período"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..416cbc0
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hora"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuto"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Segundo"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="many">%d horas</item>
+ <item quantity="other">%d horas</item>
+ <item quantity="one">%d hora</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="many">%d minutos</item>
+ <item quantity="other">%d minutos</item>
+ <item quantity="one">%d minuto</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="many">%d segundos</item>
+ <item quantity="other">%d segundos</item>
+ <item quantity="one">%d segundo</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Período"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"Dia"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"Mês"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Ano"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Seguinte"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000..77c3710
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hora"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuto"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Segundo"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d hora</item>
+ <item quantity="many">%d de horas</item>
+ <item quantity="other">%d horas</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d minuto</item>
+ <item quantity="many">%d de minutos</item>
+ <item quantity="other">%d minutos</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d segundo</item>
+ <item quantity="many">%d de segundos</item>
+ <item quantity="other">%d segundos</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Período"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ro/strings.xml b/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000..f9723df
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Oră"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minut"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Secundă"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="few">%d ore</item>
+ <item quantity="other">%d de ore</item>
+ <item quantity="one">%d oră</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="few">%d minute</item>
+ <item quantity="other">%d de minute</item>
+ <item quantity="one">%d minut</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="few">%d secunde</item>
+ <item quantity="other">%d de secunde</item>
+ <item quantity="one">%d secundă</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Perioada"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmă"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ru/strings.xml b/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..e01e3c3
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Часы"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Минуты"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Секунды"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d час</item>
+ <item quantity="few">%d часа</item>
+ <item quantity="many">%d часов</item>
+ <item quantity="other">%d часа</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d минута</item>
+ <item quantity="few">%d минуты</item>
+ <item quantity="many">%d минут</item>
+ <item quantity="other">%d минуты</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d секунда</item>
+ <item quantity="few">%d секунды</item>
+ <item quantity="many">%d секунд</item>
+ <item quantity="other">%d секунды</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Период"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Подтвердить"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-si/strings.xml b/wear/compose/compose-material3/src/main/res/values-si/strings.xml
new file mode 100644
index 0000000..6f3d4c6
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-si/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"පැය"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"විනාඩි"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"තත්පර"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">පැය %d</item>
+ <item quantity="other">පැය %d</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">මිනිත්තු %d</item>
+ <item quantity="other">මිනිත්තු %d</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">තත්පර %d</item>
+ <item quantity="other">තත්පර %d</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"කාල පරිච්ඡේදය"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"තහවුරු කරන්න"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-sk/strings.xml b/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000..8840530
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Hodina"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minúta"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekunda"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="few">%d hodiny</item>
+ <item quantity="many">%d hodiny</item>
+ <item quantity="other">%d hodín</item>
+ <item quantity="one">%d hodina</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="few">%d minúty</item>
+ <item quantity="many">%d minúty</item>
+ <item quantity="other">%d minút</item>
+ <item quantity="one">%d minúta</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="few">%d sekundy</item>
+ <item quantity="many">%d sekundy</item>
+ <item quantity="other">%d sekúnd</item>
+ <item quantity="one">%d sekunda</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Obdobie"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdiť"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-sl/strings.xml b/wear/compose/compose-material3/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000..cc79d76
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-sl/strings.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Ura"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuta"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekunda"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d ura</item>
+ <item quantity="two">%d uri</item>
+ <item quantity="few">%d ure</item>
+ <item quantity="other">%d ur</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d minuta</item>
+ <item quantity="two">%d minuti</item>
+ <item quantity="few">%d minute</item>
+ <item quantity="other">%d minut</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d sekunda</item>
+ <item quantity="two">%d sekundi</item>
+ <item quantity="few">%d sekunde</item>
+ <item quantity="other">%d sekund</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Pika"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"Dan"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"Mesec"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Leto"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potrdi"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Naprej"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-sq/strings.xml b/wear/compose/compose-material3/src/main/res/values-sq/strings.xml
new file mode 100644
index 0000000..411d53e
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-sq/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Orë"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuta"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekonda"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d orë</item>
+ <item quantity="one">%d orë</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minuta</item>
+ <item quantity="one">%d minutë</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d sekonda</item>
+ <item quantity="one">%d sekondë</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Periudha"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Konfirmo"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-sr/strings.xml b/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000..661b6d2
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Сат"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Минут"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Секунда"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d сат</item>
+ <item quantity="few">%d сата</item>
+ <item quantity="other">%d сати</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d минут</item>
+ <item quantity="few">%d минута</item>
+ <item quantity="other">%d минута</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d секунда</item>
+ <item quantity="few">%d секунде</item>
+ <item quantity="other">%d секунди</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Период"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Потврди"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-sv/strings.xml b/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
new file mode 100644
index 0000000..b395ba1d
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Timme"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minut"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekund"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d timmar</item>
+ <item quantity="one">%d timme</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d minuter</item>
+ <item quantity="one">%d minut</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d sekunder</item>
+ <item quantity="one">%d sekund</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Punkt"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bekräfta"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-sw/strings.xml b/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
new file mode 100644
index 0000000..aad1959
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Saa"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Dakika"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Sekunde"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">Saa %d</item>
+ <item quantity="one">Saa %d</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">Dakika %d</item>
+ <item quantity="one">Dakika %d</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">Sekunde %d</item>
+ <item quantity="one">Sekunde %d</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Kipindi"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Thibitisha"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ta/strings.xml b/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
new file mode 100644
index 0000000..aacd3cd
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"மணிநேரம்"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"நிமிடம்"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"வினாடி"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d மணிநேரம்</item>
+ <item quantity="one">%d மணிநேரம்</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d நிமிடங்கள்</item>
+ <item quantity="one">%d நிமிடம்</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d வினாடிகள்</item>
+ <item quantity="one">%d வினாடி</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"கால இடைவெளி"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"உறுதிசெய்யும்"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-te/strings.xml b/wear/compose/compose-material3/src/main/res/values-te/strings.xml
new file mode 100644
index 0000000..2374063
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-te/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"గంట"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"నిమిషం"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"సెకను"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d గంటలు</item>
+ <item quantity="one">%d గంట</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d నిమిషాలు</item>
+ <item quantity="one">%d నిమిషం</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d సెకన్లు</item>
+ <item quantity="one">%d సెకను</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"వ్యవధి"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"రోజు"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"నెల"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"సంవత్సరం"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"నిర్ధారించండి"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"తర్వాత"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-th/strings.xml b/wear/compose/compose-material3/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000..d1ad793
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-th/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"ชั่วโมง"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"นาที"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"วินาที"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d ชั่วโมง</item>
+ <item quantity="one">%d ชั่วโมง</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d นาที</item>
+ <item quantity="one">%d นาที</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d วินาที</item>
+ <item quantity="one">%d วินาที</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"ระยะเวลา"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"วัน"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"เดือน"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"ปี"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"ยืนยัน"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"ถัดไป"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-tl/strings.xml b/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
new file mode 100644
index 0000000..fe20f09
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Oras"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Minuto"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Segundo"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d Oras</item>
+ <item quantity="other">%d na Oras</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d Minuto</item>
+ <item quantity="other">%d na Minuto</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d Segundo</item>
+ <item quantity="other">%d na Segundo</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Panahon"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"Araw"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"Buwan"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Taon"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Kumpirmahin"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Susunod"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-tr/strings.xml b/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..3ec35be
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Saat"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Dakika"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Saniye"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d Saat</item>
+ <item quantity="one">%d Saat</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d Dakika</item>
+ <item quantity="one">%d Dakika</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d Saniye</item>
+ <item quantity="one">%d Saniye</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Aralık"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Onayla"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-uk/strings.xml b/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000..bb037ea
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Година"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Хвилина"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Секунда"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">%d година</item>
+ <item quantity="few">%d години</item>
+ <item quantity="many">%d годин</item>
+ <item quantity="other">%d години</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">%d хвилина</item>
+ <item quantity="few">%d хвилини</item>
+ <item quantity="many">%d хвилин</item>
+ <item quantity="other">%d хвилини</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">%d секунда</item>
+ <item quantity="few">%d секунди</item>
+ <item quantity="many">%d секунд</item>
+ <item quantity="other">%d секунди</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Період"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Підтвердити"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-ur/strings.xml b/wear/compose/compose-material3/src/main/res/values-ur/strings.xml
new file mode 100644
index 0000000..6f902ab
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-ur/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"گھنٹہ"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"منٹ"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"دوسرا"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d گھنٹے</item>
+ <item quantity="one">%d گھنٹہ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d منٹ</item>
+ <item quantity="one">%d منٹ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d سیکنڈ</item>
+ <item quantity="one">%d سیکنڈ</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"وقفہ"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"تصدیق کریں"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-uz/strings.xml b/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
new file mode 100644
index 0000000..4b1121e
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Soat"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Daqiqa"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Soniya"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d soat</item>
+ <item quantity="one">%d soat</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d daqiqa</item>
+ <item quantity="one">%d daqiqa</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d soniya</item>
+ <item quantity="one">%d soniya</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Oraliq"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"Kun"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"Oy"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Yil"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Tasdiqlash"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Keyingisi"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-vi/strings.xml b/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000..3b40f28
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Giờ"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Phút"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Giây"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d giờ</item>
+ <item quantity="one">%d giờ</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d phút</item>
+ <item quantity="one">%d phút</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d giây</item>
+ <item quantity="one">%d giây</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Khoảng thời gian"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Xác nhận"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..14ce25a
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"小时"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"分钟"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"秒"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d 小时</item>
+ <item quantity="one">%d 小时</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d 分钟</item>
+ <item quantity="one">%d 分钟</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d 秒</item>
+ <item quantity="one">%d 秒</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"时段"</string>
+ <string name="wear_m3c_date_picker_day" msgid="8932770593644830235">"日"</string>
+ <string name="wear_m3c_date_picker_month" msgid="5962969526377479136">"月"</string>
+ <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"年"</string>
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"确认"</string>
+ <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"下一个"</string>
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..0d30e02
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"小時"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"分鐘"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"秒"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d 個鐘</item>
+ <item quantity="one">%d 個鐘</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d 分鐘</item>
+ <item quantity="one">%d 分鐘</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d 秒</item>
+ <item quantity="one">%d 秒</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"時段"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"確認"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..3e569a9
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"小時"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"分鐘"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"秒"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="other">%d 小時</item>
+ <item quantity="one">%d 小時</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="other">%d 分鐘</item>
+ <item quantity="one">%d 分鐘</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="other">%d 秒</item>
+ <item quantity="one">%d 秒</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"期間"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"確認"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values-zu/strings.xml b/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
new file mode 100644
index 0000000..43167ed
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="wear_m3c_time_picker_hour" msgid="5670838450714030035">"Ihora"</string>
+ <string name="wear_m3c_time_picker_minute" msgid="2847700380677127030">"Umzuzu"</string>
+ <string name="wear_m3c_time_picker_second" msgid="5551916170669814925">"Umzuzwana"</string>
+ <plurals name="wear_m3c_time_picker_hours_content_description" formatted="false" msgid="7688673698789346225">
+ <item quantity="one">Amahora angu-%d</item>
+ <item quantity="other">Amahora angu-%d</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_minutes_content_description" formatted="false" msgid="8268405448590438607">
+ <item quantity="one">Imizuzu engu-%d</item>
+ <item quantity="other">Imizuzu engu-%d</item>
+ </plurals>
+ <plurals name="wear_m3c_time_picker_seconds_content_description" formatted="false" msgid="1073969431850983434">
+ <item quantity="one">Imizuzwana engu-%d</item>
+ <item quantity="other">Imizuzwana engu-%d</item>
+ </plurals>
+ <string name="wear_m3c_time_picker_period" msgid="5567285614451063120">"Isikhathi"</string>
+ <!-- no translation found for wear_m3c_date_picker_day (8932770593644830235) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_month (5962969526377479136) -->
+ <skip />
+ <!-- no translation found for wear_m3c_date_picker_year (4697690064312147449) -->
+ <skip />
+ <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Qinisekisa"</string>
+ <!-- no translation found for wear_m3c_picker_next_button_content_description (3346011303652897029) -->
+ <skip />
+</resources>
diff --git a/wear/compose/compose-material3/src/main/res/values/strings.xml b/wear/compose/compose-material3/src/main/res/values/strings.xml
index 35e6bd7..868ca2b 100644
--- a/wear/compose/compose-material3/src/main/res/values/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values/strings.xml
@@ -31,5 +31,13 @@
<item quantity="other">%d Seconds</item>
</plurals>
<string description="Content description of the period picker in TimePickerWith12HourClock. It lets the user select the period for 12H time format. [CHAR_LIMIT=NONE]" name="wear_m3c_time_picker_period">Period</string>
+ <string description="Lets the user know that this picker is to change the value of the day date unit. Appears on the DatePicker component, on top of the day picker. [CHAR_LIMIT=8]" name="wear_m3c_date_picker_day">Day</string>
+ <string description="Lets the user know that this picker is to change the value of the month date unit. Appears on the DatePicker component, on top of the month picker. [CHAR_LIMIT=8]" name="wear_m3c_date_picker_month">Month</string>
+ <string description="Lets the user know that this picker is to change the value of the year date unit. Appears on the DatePicker component, on top of the year picker. [CHAR_LIMIT=8]" name="wear_m3c_date_picker_year">Year</string>
<string description="Content description of the confirm button of DatePicker and TimePicker components. It lets the user confirm the date or time selected. [CHAR_LIMIT=NONE]" name="wear_m3c_picker_confirm_button_content_description">Confirm</string>
-</resources>
\ No newline at end of file
+ <string description="Content description of the next button of DatePicker and TimePicker components. It lets the user to move to the next picker. [CHAR_LIMIT=NONE]" name="wear_m3c_picker_next_button_content_description">Next</string>
+
+ <string description="A message which is used to indicate that an action failed in a FailureConfirmation [CHAR_LIMIT=12]" name="wear_m3c_confirmation_failure_message">Failed</string>
+ <string description="A message which is used to indicate that an action succeeded in a SuccessConfirmation [CHAR_LIMIT=12]" name="wear_m3c_confirmation_success_message">Success</string>
+ <string description="A message which is used to indicate than the user should continue their action on their phone, used in OpenOnPhone component [CHAR_LIMIT=12]" name="wear_m3c_open_on_phone">Open on phone</string>
+</resources>
diff --git a/wear/compose/compose-navigation/build.gradle b/wear/compose/compose-navigation/build.gradle
index 2e021e1..bb35fa2 100644
--- a/wear/compose/compose-navigation/build.gradle
+++ b/wear/compose/compose-navigation/build.gradle
@@ -31,8 +31,8 @@
}
dependencies {
- api("androidx.compose.ui:ui:1.7.0-rc01")
- api("androidx.compose.runtime:runtime:1.7.0-rc01")
+ api("androidx.compose.ui:ui:1.7.0")
+ api("androidx.compose.runtime:runtime:1.7.0")
api("androidx.navigation:navigation-runtime:2.6.0")
api(project(":wear:compose:compose-material"))
api("androidx.activity:activity-compose:1.7.0")
diff --git a/wear/compose/compose-ui-tooling/build.gradle b/wear/compose/compose-ui-tooling/build.gradle
index d49acb1..5dcfeea 100644
--- a/wear/compose/compose-ui-tooling/build.gradle
+++ b/wear/compose/compose-ui-tooling/build.gradle
@@ -32,7 +32,7 @@
dependencies {
api("androidx.annotation:annotation:1.8.1")
- api("androidx.compose.ui:ui-tooling-preview:1.7.0-rc01")
+ api("androidx.compose.ui:ui-tooling-preview:1.7.0")
implementation(libs.kotlinStdlib)
implementation("androidx.wear:wear-tooling-preview:1.0.0")
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index 6a56c60..8886921 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -26,8 +26,8 @@
defaultConfig {
applicationId "androidx.wear.compose.integration.demos"
minSdk 25
- versionCode 36
- versionName "1.36"
+ versionCode 37
+ versionName "1.37"
}
buildTypes {
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
index 650517cd..a674c88 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
@@ -17,6 +17,7 @@
package androidx.wear.compose.integration.demos
import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -25,8 +26,10 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.SwipeToDismissBoxState
import androidx.wear.compose.foundation.SwipeToDismissKeys
import androidx.wear.compose.foundation.SwipeToDismissValue
@@ -123,6 +126,8 @@
modifier = Modifier.fillMaxWidth().testTag(DemoListTag),
state = state,
autoCentering = AutoCenteringParams(itemIndex = if (category.demos.size >= 2) 2 else 1),
+ contentPadding =
+ PaddingValues(horizontal = LocalConfiguration.current.screenWidthDp.dp * 0.052f),
) {
item {
ListHeader {
diff --git a/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt b/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
index c879bf4..9f3ead4 100644
--- a/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
@@ -6,6 +6,18 @@
method @UiThread public void startEvaluation();
}
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface DynamicTypeAnimator {
+ method public void advanceToAnimationTime(long);
+ method public Object? getCurrentValue();
+ method public long getDurationMs();
+ method public Object? getEndValue();
+ method public long getStartDelayMs();
+ method public Object? getStartValue();
+ method public android.animation.TypeEvaluator<? extends java.lang.Object!> getTypeEvaluator();
+ method public void setFloatValues(float...);
+ method public void setIntValues(int...);
+ }
+
public abstract class DynamicTypeBindingRequest {
method public static androidx.wear.protolayout.expression.pipeline.DynamicTypeBindingRequest forDynamicBool(androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool, java.util.concurrent.Executor, androidx.wear.protolayout.expression.pipeline.DynamicTypeValueReceiver<java.lang.Boolean!>);
method public static androidx.wear.protolayout.expression.pipeline.DynamicTypeBindingRequest forDynamicColor(androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor, java.util.concurrent.Executor, androidx.wear.protolayout.expression.pipeline.DynamicTypeValueReceiver<java.lang.Integer!>);
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeAnimator.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeAnimator.java
new file mode 100644
index 0000000..eb56683
--- /dev/null
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeAnimator.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2024 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.wear.protolayout.expression.pipeline;
+
+import android.animation.TypeEvaluator;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+/**
+ * DynamicTypeAnimator interface defines the methods and properties of ProtoLayout animation.
+ *
+ * <p>The following classes implement the DynamicTypeAnimator interface:
+ *
+ * <ul>
+ * <li>{@link QuotaAwareAnimator}
+ * <li>{@link QuotaAwareAnimatorWithAux}
+ * </ul>
+ *
+ * <p>This interface allows to inspect animation and modify it. It can set new float and int values,
+ * and set a timeframe for animation. This class is intended to be used by Ui-tooling in Android
+ * Studio
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+public interface DynamicTypeAnimator {
+
+ /**
+ * Gets the type evaluator used for interpolating values in this animation.
+ *
+ * @return The type evaluator used for interpolation.
+ */
+ @NonNull
+ TypeEvaluator<?> getTypeEvaluator();
+
+ /**
+ * Sets the float values that this animation will animate between.
+ *
+ * @param values The float values to animate between.
+ * @throws IllegalArgumentException if this {@link DynamicTypeAnimator} is not configured with a
+ * suitable {@link TypeEvaluator} for float values (e.g., {@link FloatEvaluator}).
+ */
+ void setFloatValues(@NonNull float... values);
+
+ /**
+ * Sets the integer values that this animation will animate between.
+ *
+ * @param values The integer values to animate between.
+ * @throws IllegalArgumentException if this {@link DynamicTypeAnimator} is not configured with a
+ * suitable {@link TypeEvaluator} for integer values (e.g., {@link IntEvaluator} or {@link
+ * ArgbEvaluator}).
+ */
+ void setIntValues(@NonNull int... values);
+
+ /**
+ * Advances the animation to the specified time.
+ *
+ * @param newTime The new time in milliseconds from animation start.
+ */
+ void advanceToAnimationTime(long newTime);
+
+ /**
+ * Gets the start value of the animation.
+ *
+ * @return The start value of the animation or null if value wasn't set.
+ */
+ @Nullable
+ Object getStartValue();
+
+ /**
+ * Gets the end value of the animation.
+ *
+ * @return The end value of the animation.
+ */
+ @Nullable
+ Object getEndValue();
+
+ /**
+ * Gets the last value of the animated property at the current time in the animation.
+ *
+ * @return The last calculated animated value or null if value wasn't set.
+ */
+ @Nullable
+ Object getCurrentValue();
+
+ /**
+ * Gets the duration of the animation, in milliseconds.
+ *
+ * @return The duration of the animation.
+ */
+ long getDurationMs();
+
+ /**
+ * Gets the start delay of the animation, in milliseconds.
+ *
+ * @return The start delay of the animation.
+ */
+ long getStartDelayMs();
+}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimator.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimator.java
index fa7fdbd..b9671b6 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimator.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimator.java
@@ -30,6 +30,7 @@
import android.os.Looper;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.core.os.HandlerCompat;
import androidx.wear.protolayout.expression.pipeline.AnimationsHelper.RepeatDelays;
@@ -42,14 +43,18 @@
* quota manager allows. If not, non infinite animation will jump to an end. Any existing listeners
* on wrapped {@link Animator} will be replaced.
*/
-class QuotaAwareAnimator {
+class QuotaAwareAnimator implements DynamicTypeAnimator {
@NonNull protected final ValueAnimator mAnimator;
@NonNull protected final QuotaManager mQuotaManager;
@NonNull protected final QuotaReleasingAnimatorListener mListener;
@NonNull protected final Handler mUiHandler;
- private long mStartDelay = 0;
+ private final long mStartDelay;
protected Runnable mAcquireQuotaAndAnimateRunnable = this::acquireQuotaAndAnimate;
@NonNull protected final TypeEvaluator<?> mEvaluator;
+ @Nullable protected Object mLastAnimatedValue;
+
+ @Nullable private Object mStartValue = null; // To cache the start value
+ @Nullable private Object mEndValue = null; // To cache the end value
interface UpdateCallback {
void onUpdate(@NonNull Object animatedValue);
@@ -96,6 +101,12 @@
mEvaluator = evaluator;
}
+ @NonNull
+ @Override
+ public TypeEvaluator<?> getTypeEvaluator() {
+ return mEvaluator;
+ }
+
/**
* Adds a listener that is sent update events through the life of the animation. This method is
* called on every frame of the animation after the values of the animation have been
@@ -103,7 +114,10 @@
*/
void addUpdateCallback(@NonNull UpdateCallback updateCallback) {
mAnimator.addUpdateListener(
- animation -> updateCallback.onUpdate(animation.getAnimatedValue()));
+ animation -> {
+ mLastAnimatedValue = animation.getAnimatedValue();
+ updateCallback.onUpdate(mLastAnimatedValue);
+ });
}
/**
@@ -111,8 +125,11 @@
*
* @param values A set of values that the animation will animate between over time.
*/
- void setFloatValues(float... values) {
+ @Override
+ public void setFloatValues(@NonNull float... values) {
setFloatValues(mAnimator, mEvaluator, values);
+ mStartValue = values[0];
+ mEndValue = values[values.length - 1];
}
protected static void setFloatValues(
@@ -134,8 +151,33 @@
*
* @param values A set of values that the animation will animate between over time.
*/
- void setIntValues(int... values) {
+ @Override
+ public void setIntValues(@NonNull int... values) {
setIntValues(mAnimator, mEvaluator, values);
+ mStartValue = values[0];
+ mEndValue = values[values.length - 1];
+ }
+
+ /**
+ * Gets the start value of the animation.
+ *
+ * @return The start value of the animation or null if value wasn't set.
+ */
+ @Override
+ @Nullable
+ public Object getStartValue() {
+ return mStartValue;
+ }
+
+ /**
+ * Gets the end value of the animation.
+ *
+ * @return The end value of the animation.
+ */
+ @Override
+ @Nullable
+ public Object getEndValue() {
+ return mEndValue;
}
protected static void setIntValues(
@@ -251,6 +293,28 @@
mAnimator.end();
}
+ @Override
+ public void advanceToAnimationTime(long newTime) {
+ long adjustedTime = newTime - mStartDelay;
+ mAnimator.setCurrentPlayTime(adjustedTime);
+ }
+
+ @Nullable
+ @Override
+ public Object getCurrentValue() {
+ return mLastAnimatedValue;
+ }
+
+ @Override
+ public long getDurationMs() {
+ return mAnimator.getDuration();
+ }
+
+ @Override
+ public long getStartDelayMs() {
+ return mStartDelay;
+ }
+
/** Returns whether the animator in this class has an infinite duration. */
protected boolean isInfiniteAnimator() {
return mAnimator.getTotalDuration() == Animator.DURATION_INFINITE;
@@ -276,23 +340,20 @@
* animation.
*/
protected static final class QuotaReleasingAnimatorListener extends AnimatorListenerAdapter {
- @NonNull
- private final QuotaManager mQuotaManager;
+ @NonNull private final QuotaManager mQuotaManager;
// We need to keep track of whether the animation has started because pipeline has initiated
// and it has received quota, or it is skipped by calling {@link android.animation
// .Animator#end()} because no quota is available.
- @NonNull
- final AtomicBoolean mIsUsingQuota = new AtomicBoolean(false);
+ @NonNull final AtomicBoolean mIsUsingQuota = new AtomicBoolean(false);
private final int mRepeatMode;
private final long mForwardRepeatDelay;
private final long mReverseRepeatDelay;
- @NonNull
- private final Handler mHandler;
- @NonNull
- Runnable mResumeRepeatRunnable;
+ @NonNull private final Handler mHandler;
+ @NonNull Runnable mResumeRepeatRunnable;
private boolean mIsReverse;
+
/**
* Only intended to be true with {@link QuotaAwareAnimatorWithAux} to play main and aux
* animators alternately, the pause and resume is still required to swap animators even
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimatorWithAux.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimatorWithAux.java
index 3d050a9..fda3478 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimatorWithAux.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimatorWithAux.java
@@ -80,44 +80,52 @@
mAnimator.addUpdateListener(
animation -> {
if (!mSuppressForwardUpdate && !mAnimator.isPaused()) {
- updateCallback.onUpdate(animation.getAnimatedValue());
+ mLastAnimatedValue = animation.getAnimatedValue();
+ updateCallback.onUpdate(mLastAnimatedValue);
}
});
mAuxAnimator.addUpdateListener(
animation -> {
if (!mSuppressReverseUpdate && !mAuxAnimator.isPaused()) {
- updateCallback.onUpdate(animation.getAnimatedValue());
+ mLastAnimatedValue = animation.getAnimatedValue();
+ updateCallback.onUpdate(mLastAnimatedValue);
}
});
}
@Override
- void setFloatValues(float... values) {
+ public void setFloatValues(@NonNull float... values) {
super.setFloatValues(values);
- // reverse the value array
+ // Create a copy of the values array before reversing it
+ float[] reversedValues = values.clone();
+
+ // reverse the copied array
float temp;
- for (int i = 0; i < values.length / 2; i++) {
- temp = values[i];
- values[i] = values[values.length - 1 - i];
- values[values.length - 1 - i] = temp;
+ for (int i = 0; i < reversedValues.length / 2; i++) {
+ temp = reversedValues[i];
+ reversedValues[i] = reversedValues[reversedValues.length - 1 - i];
+ reversedValues[reversedValues.length - 1 - i] = temp;
}
- setFloatValues(mAuxAnimator, mEvaluator, values);
+ setFloatValues(mAuxAnimator, mEvaluator, reversedValues);
}
@Override
- void setIntValues(int... values) {
+ public void setIntValues(@NonNull int... values) {
super.setIntValues(values);
- // reverse the value array
+ // Create a copy of the values array before reversing it
+ int[] reversedValues = values.clone();
+
+ // reverse the copied array
int temp;
- for (int i = 0; i < values.length / 2; i++) {
- temp = values[i];
- values[i] = values[values.length - 1 - i];
- values[values.length - 1 - i] = temp;
+ for (int i = 0; i < reversedValues.length / 2; i++) {
+ temp = reversedValues[i];
+ reversedValues[i] = reversedValues[reversedValues.length - 1 - i];
+ reversedValues[reversedValues.length - 1 - i] = temp;
}
- setIntValues(mAuxAnimator, mEvaluator, values);
+ setIntValues(mAuxAnimator, mEvaluator, reversedValues);
}
@Override
@@ -182,4 +190,20 @@
&& mAuxAnimator.isPaused()
&& !HandlerCompat.hasCallbacks(mUiHandler, mAuxListener.mResumeRepeatRunnable);
}
+
+ @Override
+ public void advanceToAnimationTime(long newTime) {
+ if (newTime < mAuxAnimator.getStartDelay()) {
+ super.advanceToAnimationTime(newTime);
+ } else {
+ // Adjust time for the auxiliary animator
+ long adjustedTime = newTime - mAuxAnimator.getStartDelay();
+ mAuxAnimator.setCurrentPlayTime(adjustedTime);
+ }
+ }
+
+ @Override
+ public long getDurationMs() {
+ return mAnimator.getDuration() + mAuxAnimator.getDuration();
+ }
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimatorTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimatorTest.java
new file mode 100644
index 0000000..6843fac
--- /dev/null
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimatorTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2024 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.wear.protolayout.expression.pipeline;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ArgbEvaluator;
+import android.animation.FloatEvaluator;
+import android.animation.IntEvaluator;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.wear.protolayout.expression.proto.AnimationParameterProto.AnimationSpec;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(AndroidJUnit4.class)
+public class QuotaAwareAnimatorTest {
+
+ private static final int ANIMATION_DURATION = 500;
+ private static final int ANIMATION_START_DELAY = 100;
+ private static final float[] FLOAT_VALUES = {0f, 5f, 10f};
+ private static final int[] INT_VALUES = {0, 50, 100};
+ @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+ @Mock private QuotaManager mMockQuotaManager;
+ private AnimationSpec mDefaultAnimationSpec;
+ private AnimationSpec mAnimationSpecWithDuration;
+
+ @Before
+ public void setUp() {
+ // Create AnimationSpec instances
+ mDefaultAnimationSpec = AnimationSpec.newBuilder().build();
+ mAnimationSpecWithDuration =
+ AnimationSpec.newBuilder()
+ .setDurationMillis(ANIMATION_DURATION)
+ .setStartDelayMillis(ANIMATION_START_DELAY)
+ .build();
+ }
+
+ @Test
+ public void getTypeEvaluator_returnsCorrectEvaluator() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mDefaultAnimationSpec, new FloatEvaluator());
+
+ assertThat(animator.getTypeEvaluator()).isInstanceOf(FloatEvaluator.class);
+ }
+
+ @Test
+ public void setFloatValues_updatesAnimatorValues() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mDefaultAnimationSpec, new FloatEvaluator());
+
+ animator.setFloatValues(FLOAT_VALUES);
+ animator.addUpdateCallback(value -> {}); // Add an empty update callback
+
+ // Check the animated value at the beginning of the animation
+ animator.advanceToAnimationTime(0);
+ assertThat(animator.getCurrentValue()).isEqualTo(FLOAT_VALUES[0]);
+
+ // Check the animated value at the end of the animation
+ animator.advanceToAnimationTime(ANIMATION_DURATION); // Assuming default 300ms duration
+ assertThat(animator.getCurrentValue()).isEqualTo(FLOAT_VALUES[FLOAT_VALUES.length - 1]);
+ }
+
+ @Test
+ public void setFloatValues_cancelsAnimator() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mDefaultAnimationSpec, new FloatEvaluator());
+ animator.mAnimator.start(); // Simulate running animator
+
+ animator.setFloatValues(FLOAT_VALUES);
+
+ assertThat(animator.mAnimator.isStarted()).isFalse();
+ }
+
+ @Test
+ public void setFloatValues_throwsExceptionWithIncorrectEvaluator() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mDefaultAnimationSpec, new IntEvaluator());
+
+ assertThrows(IllegalArgumentException.class, () -> animator.setFloatValues(FLOAT_VALUES));
+ }
+
+ @Test
+ public void setIntValues_updatesAnimatorValues() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mDefaultAnimationSpec, new IntEvaluator());
+
+ animator.setIntValues(INT_VALUES);
+ animator.addUpdateCallback(value -> {}); // Add an empty update callback
+
+ // Check the animated value at the beginning of the animation
+ animator.advanceToAnimationTime(0);
+ assertThat(animator.getCurrentValue()).isEqualTo(INT_VALUES[0]);
+
+ // Check the animated value at the end of the animation
+ animator.advanceToAnimationTime(ANIMATION_DURATION);
+ assertThat(animator.getCurrentValue()).isEqualTo(INT_VALUES[INT_VALUES.length - 1]);
+ }
+
+ @Test
+ public void setIntValues_cancelsAnimator() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mDefaultAnimationSpec, new IntEvaluator());
+ animator.mAnimator.start(); // Simulate running animator
+
+ animator.setIntValues(INT_VALUES);
+
+ assertThat(animator.mAnimator.isStarted()).isFalse();
+ }
+
+ @Test
+ public void setIntValues_throwsExceptionWithIncorrectEvaluator() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mDefaultAnimationSpec, new FloatEvaluator());
+
+ assertThrows(IllegalArgumentException.class, () -> animator.setIntValues(INT_VALUES));
+ }
+
+ @Test
+ public void setIntValues_worksWithArgbEvaluator() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mDefaultAnimationSpec, new ArgbEvaluator());
+
+ animator.setIntValues(INT_VALUES); // Should not throw an exception
+ }
+
+ @Test
+ public void advanceToAnimationTime_setsCurrentPlayTime() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mAnimationSpecWithDuration, new FloatEvaluator());
+ long newTime = 250L;
+
+ animator.advanceToAnimationTime(newTime);
+
+ assertThat(animator.mAnimator.getCurrentPlayTime())
+ .isEqualTo(newTime - ANIMATION_START_DELAY);
+ }
+
+ @Test
+ public void getPropertyValuesHolders_returnsAnimatorValues() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mDefaultAnimationSpec, new FloatEvaluator());
+ animator.setFloatValues(FLOAT_VALUES);
+
+ Object startValue = animator.getStartValue();
+ Object endValue = animator.getEndValue();
+
+ assertThat(startValue).isEqualTo(FLOAT_VALUES[0]);
+ assertThat(endValue).isEqualTo(FLOAT_VALUES[FLOAT_VALUES.length - 1]);
+ }
+
+ @Test
+ public void getCurrentValue_returnsCorrectValue() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mDefaultAnimationSpec, new FloatEvaluator());
+ animator.setFloatValues(0f, 10f);
+ animator.addUpdateCallback(value -> {}); // Add an empty update callback
+ animator.mAnimator.setCurrentPlayTime(150); // Halfway through the default 300ms duration
+
+ Object lastValue = animator.getCurrentValue();
+
+ assertThat(lastValue).isEqualTo(5f); // Expected interpolated value
+ }
+
+ @Test
+ public void getDuration_returnsAnimatorDurationMs() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mAnimationSpecWithDuration, new FloatEvaluator());
+
+ long duration = animator.getDurationMs();
+
+ assertThat(duration).isEqualTo(ANIMATION_DURATION);
+ }
+
+ @Test
+ public void getStartDelay_returnsAnimatorStartDelayMs() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mAnimationSpecWithDuration, new FloatEvaluator());
+
+ long startDelay = animator.getStartDelayMs();
+
+ assertThat(startDelay).isEqualTo(ANIMATION_START_DELAY);
+ }
+
+ @Test
+ public void tryStartAnimation_acquiresQuotaAndStarts_whenQuotaAvailable() {
+ when(mMockQuotaManager.tryAcquireQuota(anyInt()))
+ .thenReturn(true); // Simulate quota available
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mDefaultAnimationSpec, new FloatEvaluator());
+ animator.setFloatValues(FLOAT_VALUES);
+ animator.addUpdateCallback(value -> {}); // Add an empty update callback
+
+ animator.tryStartAnimation();
+
+ verify(mMockQuotaManager).tryAcquireQuota(1); // Verify quota acquisition
+ assertThat(animator.mAnimator.isStarted()).isTrue(); // Verify animator started
+ assertThat(animator.mListener.mIsUsingQuota.get()).isTrue(); // Verify quota flag is set
+ }
+
+ @Test
+ public void testStartAndEndValueCaching_FloatValues() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mAnimationSpecWithDuration, new FloatEvaluator());
+ float[] values = {10f, 20f, 30f};
+ animator.setFloatValues(values);
+
+ assertEquals(10f, animator.getStartValue());
+ assertEquals(30f, animator.getEndValue());
+ }
+
+ @Test
+ public void testStartAndEndValueCaching_IntValues() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mAnimationSpecWithDuration, new IntEvaluator());
+ int[] values = {5, 15, 25};
+ animator.setIntValues(values);
+
+ assertEquals(5, animator.getStartValue());
+ assertEquals(25, animator.getEndValue());
+ }
+
+ @Test
+ public void testStartAndEndValue_BeforeSettingValues() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mAnimationSpecWithDuration, new FloatEvaluator());
+ assertNull(animator.getStartValue());
+ assertNull(animator.getEndValue());
+ }
+
+ @Test
+ public void testStartAndEndValue_AfterResettingValues() {
+ QuotaAwareAnimator animator =
+ new QuotaAwareAnimator(
+ mMockQuotaManager, mAnimationSpecWithDuration, new IntEvaluator());
+ animator.setIntValues(1, 2);
+ animator.setIntValues(3, 4);
+
+ assertEquals(3, animator.getStartValue());
+ assertEquals(4, animator.getEndValue());
+ }
+}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimatorWithAuxTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimatorWithAuxTest.java
new file mode 100644
index 0000000..c1dccf6
--- /dev/null
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/QuotaAwareAnimatorWithAuxTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2024 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.wear.protolayout.expression.pipeline;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.animation.FloatEvaluator;
+import android.animation.IntEvaluator;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.wear.protolayout.expression.proto.AnimationParameterProto.AnimationSpec;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(AndroidJUnit4.class)
+public class QuotaAwareAnimatorWithAuxTest {
+ private static final int ANIMATION_DURATION = 500;
+ private static final int ANIMATION_START_DELAY = 100;
+ private static final float[] FLOAT_VALUES = {0f, 5f, 10f};
+ private static final int[] INT_VALUES = {0, 50, 100};
+
+ @Mock private QuotaManager mMockQuotaManager;
+
+ private AnimationSpec mDefaultAnimationSpec;
+ private AnimationSpec mAnimationSpecWithDuration;
+
+ @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Before
+ public void setUp() {
+ // Create AnimationSpec instances
+ mDefaultAnimationSpec = AnimationSpec.newBuilder().build();
+ mAnimationSpecWithDuration =
+ AnimationSpec.newBuilder()
+ .setDurationMillis(ANIMATION_DURATION)
+ .setStartDelayMillis(ANIMATION_START_DELAY)
+ .build();
+ }
+
+ @Test
+ public void getTypeEvaluator_returnsCorrectEvaluator_withAux() {
+ AnimationSpec auxSpec = AnimationSpec.newBuilder().build();
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager, mDefaultAnimationSpec, auxSpec, new FloatEvaluator());
+
+ assertThat(animator.getTypeEvaluator()).isInstanceOf(FloatEvaluator.class);
+ }
+
+ @Test
+ public void setFloatValues_updatesBothAnimators_withAux() {
+ long mainDuration = 300L;
+ long auxDuration = 400L;
+ long auxStartDelay = 300L;
+
+ AnimationSpec mainSpec =
+ AnimationSpec.newBuilder().setDurationMillis((int) mainDuration).build();
+ AnimationSpec auxSpec =
+ AnimationSpec.newBuilder()
+ .setDurationMillis((int) auxDuration)
+ .setStartDelayMillis((int) auxStartDelay)
+ .build();
+
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager, mainSpec, auxSpec, new FloatEvaluator());
+
+ animator.setFloatValues(FLOAT_VALUES);
+ animator.addUpdateCallback(value -> {}); // Add an empty update callback
+
+ // Check main animator at the beginning
+ animator.advanceToAnimationTime(0);
+ assertThat(animator.getCurrentValue()).isEqualTo(FLOAT_VALUES[0]);
+
+ // Check main animator at the end
+ animator.advanceToAnimationTime(mainDuration);
+ assertThat(animator.getCurrentValue()).isEqualTo(FLOAT_VALUES[FLOAT_VALUES.length - 1]);
+
+ // Check aux animator at the beginning (should be the reversed end value of the main
+ // animator)
+ animator.advanceToAnimationTime(auxStartDelay);
+ assertThat(animator.getCurrentValue()).isEqualTo(FLOAT_VALUES[FLOAT_VALUES.length - 1]);
+
+ // Check aux animator at the end (should be the reversed start value of the main animator)
+ animator.advanceToAnimationTime(auxStartDelay + auxDuration);
+ assertThat(animator.getCurrentValue()).isEqualTo(FLOAT_VALUES[0]);
+ }
+
+ @Test
+ public void setFloatValues_cancelsBothAnimators_withAux() {
+ AnimationSpec auxSpec = AnimationSpec.newBuilder().build();
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager, mDefaultAnimationSpec, auxSpec, new FloatEvaluator());
+ animator.tryStartAnimation(); // Simulate running animators
+
+ animator.setFloatValues(FLOAT_VALUES);
+
+ assertThat(animator.isRunning()).isFalse();
+ }
+
+ @Test
+ public void setFloatValues_throwsExceptionWithIncorrectEvaluator_withAux() {
+ AnimationSpec auxSpec = AnimationSpec.newBuilder().build();
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager, mDefaultAnimationSpec, auxSpec, new IntEvaluator());
+
+ assertThrows(IllegalArgumentException.class, () -> animator.setFloatValues(FLOAT_VALUES));
+ }
+
+ @Test
+ public void setIntValues_updatesBothAnimators_withAux() {
+ long mainDuration = 300L;
+ long auxDuration = 400L;
+ long auxStartDelay = 300L;
+
+ AnimationSpec mainSpec =
+ AnimationSpec.newBuilder().setDurationMillis((int) mainDuration).build();
+ AnimationSpec auxSpec =
+ AnimationSpec.newBuilder()
+ .setDurationMillis((int) auxDuration)
+ .setStartDelayMillis((int) auxStartDelay)
+ .build();
+
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager, mainSpec, auxSpec, new IntEvaluator());
+
+ animator.setIntValues(INT_VALUES);
+ animator.addUpdateCallback(value -> {}); // Add an empty update callback
+
+ // Check main animator at the beginning
+ animator.advanceToAnimationTime(0);
+ assertThat(animator.getCurrentValue()).isEqualTo(INT_VALUES[0]);
+
+ // Check main animator at the end
+ animator.advanceToAnimationTime(mainDuration);
+ assertThat(animator.getCurrentValue()).isEqualTo(INT_VALUES[INT_VALUES.length - 1]);
+
+ // Check aux animator at the beginning (should be the reversed end value of the main
+ // animator)
+ animator.advanceToAnimationTime(auxStartDelay);
+ assertThat(animator.getCurrentValue()).isEqualTo(INT_VALUES[INT_VALUES.length - 1]);
+
+ // Check aux animator at the end (should be the reversed start value of the main animator)
+ animator.advanceToAnimationTime(auxStartDelay + auxDuration);
+ assertThat(animator.getCurrentValue()).isEqualTo(INT_VALUES[0]);
+ }
+
+ @Test
+ public void setIntValues_throwsExceptionWithIncorrectEvaluator_withAux() {
+ AnimationSpec auxSpec = AnimationSpec.newBuilder().build();
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager, mDefaultAnimationSpec, auxSpec, new FloatEvaluator());
+
+ assertThrows(IllegalArgumentException.class, () -> animator.setIntValues(INT_VALUES));
+ }
+
+ @Test
+ public void setIntValues_worksWithArgbEvaluator_withAux() {
+ AnimationSpec auxSpec = AnimationSpec.newBuilder().build();
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager,
+ mDefaultAnimationSpec,
+ auxSpec,
+ AnimatableNode.ARGB_EVALUATOR);
+
+ animator.setIntValues(INT_VALUES); // Should not throw an exception
+ }
+
+ @Test
+ public void getPropertyValuesHolders_returnsMainAnimatorValues_withAux() {
+ AnimationSpec auxSpec = AnimationSpec.newBuilder().build();
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager, mDefaultAnimationSpec, auxSpec, new FloatEvaluator());
+ animator.setFloatValues(FLOAT_VALUES);
+ animator.addUpdateCallback(value -> {}); // Add an empty update callback
+
+ Object startValue = animator.getStartValue();
+ Object endValue = animator.getEndValue();
+
+ assertThat(startValue).isEqualTo(FLOAT_VALUES[0]);
+ assertThat(endValue).isEqualTo(FLOAT_VALUES[FLOAT_VALUES.length - 1]);
+ }
+
+ @Test
+ public void getCurrentValue_returnsCorrectValue_withAux() {
+ AnimationSpec auxSpec = AnimationSpec.newBuilder().build();
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager, mDefaultAnimationSpec, auxSpec, new FloatEvaluator());
+ animator.setFloatValues(0f, 10f);
+ animator.addUpdateCallback(value -> {}); // Add an empty update callback
+ animator.mAnimator.setCurrentPlayTime(150); // Halfway through the default 300ms duration
+
+ Object lastValue = animator.getCurrentValue();
+
+ assertThat(lastValue).isEqualTo(5f); // Expected interpolated value from main animator
+ }
+
+ @Test
+ public void getDuration_returnsCombinedDuration_Ms_withAux() {
+ long auxDuration = 400L;
+ AnimationSpec auxSpec =
+ AnimationSpec.newBuilder().setDurationMillis((int) auxDuration).build();
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager,
+ mAnimationSpecWithDuration,
+ auxSpec,
+ new FloatEvaluator());
+
+ long duration = animator.getDurationMs();
+
+ assertThat(duration).isEqualTo(ANIMATION_DURATION + auxDuration);
+ }
+
+ @Test
+ public void getStartDelay_returnsMainAnimatorStartDelay_Ms_withAux() {
+ AnimationSpec auxSpec = AnimationSpec.newBuilder().build();
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager,
+ mAnimationSpecWithDuration,
+ auxSpec,
+ new FloatEvaluator());
+
+ long startDelay = animator.getStartDelayMs();
+
+ assertThat(startDelay)
+ .isEqualTo(ANIMATION_START_DELAY); // Should return main animator's start delay
+ }
+
+ @Test
+ public void advanceToAnimationTime_setsCurrentPlayTime_onMainAnimator() {
+ long auxStartDelay = 300L;
+ AnimationSpec auxSpec =
+ AnimationSpec.newBuilder().setStartDelayMillis((int) auxStartDelay).build();
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager,
+ mAnimationSpecWithDuration,
+ auxSpec,
+ new FloatEvaluator());
+ animator.setFloatValues(0f, 10f); // Set values for both animators
+ animator.addUpdateCallback(value -> {}); // Add an empty update callback
+
+ long newTime = 200L; // Less than aux animator's start delay
+
+ animator.advanceToAnimationTime(newTime);
+
+ // Indirectly verify main animator's current play time
+ assertThat(animator.getCurrentValue())
+ .isEqualTo(
+ new FloatEvaluator()
+ .evaluate(
+ (newTime - ANIMATION_START_DELAY)
+ / (float) ANIMATION_DURATION,
+ 0f,
+ 10f));
+ }
+
+ @Test
+ public void advanceToAnimationTime_setsCurrentPlayTime_onAuxAnimator() {
+ long auxStartDelay = 300L;
+ long auxDuration = 400L;
+ AnimationSpec auxSpec =
+ AnimationSpec.newBuilder()
+ .setStartDelayMillis((int) auxStartDelay)
+ .setDurationMillis((int) auxDuration)
+ .build();
+ QuotaAwareAnimatorWithAux animator =
+ new QuotaAwareAnimatorWithAux(
+ mMockQuotaManager,
+ mAnimationSpecWithDuration,
+ auxSpec,
+ new FloatEvaluator());
+ animator.setFloatValues(0f, 10f); // Set values for both animators
+ animator.addUpdateCallback(value -> {}); // Add an empty update callback
+
+ long newTime = 400L; // Greater than or equal to aux animator's start delay
+
+ animator.advanceToAnimationTime(newTime);
+
+ // Indirectly verify auxiliary animator's current play time
+ assertThat(animator.getCurrentValue())
+ .isEqualTo(
+ new FloatEvaluator()
+ .evaluate(
+ (newTime - auxStartDelay) / (float) auxDuration,
+ 10f,
+ 0f)); // Reversed values in aux animator
+ }
+}
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
index f76e662..7c4af90 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
@@ -743,13 +743,16 @@
open = true)
public @interface FontFamilyName {}
- /** Font family name that uses Roboto font. Supported in renderers supporting 1.4. */
+ /**
+ * Font family name that uses Roboto font. Supported in renderers supporting 1.4, but the
+ * actual availability of this font is dependent on the devices.
+ */
@RequiresSchemaVersion(major = 1, minor = 400)
public static final String ROBOTO_FONT = "roboto";
/**
- * Font family name that uses Roboto Flex variable font. Supported in renderers
- * supporting 1.4.
+ * Font family name that uses Roboto Flex variable font. Supported in renderers supporting
+ * 1.4, but the actual availability of this font is dependent on the devices.
*/
@RequiresSchemaVersion(major = 1, minor = 400)
public static final String ROBOTO_FLEX_FONT = "roboto-flex";
diff --git a/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimelineTest.java b/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimelineTest.java
index f37b66a..98d2264 100644
--- a/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimelineTest.java
+++ b/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimelineTest.java
@@ -253,6 +253,6 @@
@SuppressWarnings("KotlinInternal")
private android.support.wearable.complications.ComplicationData asWireComplicationData(
ComplicationDataTimeline timeline) {
- return timeline.asWireComplicationData$watchface_complications_data_source_debug();
+ return timeline.asWireComplicationData$watchface_complications_data_source_release();
}
}
diff --git a/wear/watchface/watchface-style/build.gradle b/wear/watchface/watchface-style/build.gradle
index 91d5360..333ba3b7 100644
--- a/wear/watchface/watchface-style/build.gradle
+++ b/wear/watchface/watchface-style/build.gradle
@@ -87,7 +87,7 @@
// It makes sure that the apks are generated before the assets are packed.
afterEvaluate {
- tasks.named("generateDebugAndroidTestAssets").configure { it.dependsOn(copyApkTaskProvider) }
+ tasks.named("generateReleaseAndroidTestAssets").configure { it.dependsOn(copyApkTaskProvider) }
}
android {
diff --git a/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/IconWireSizeAndDimensionsTest.kt b/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/IconWireSizeAndDimensionsTest.kt
index 52f34d67..4a7305a 100644
--- a/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/IconWireSizeAndDimensionsTest.kt
+++ b/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/IconWireSizeAndDimensionsTest.kt
@@ -39,7 +39,7 @@
public fun resource() {
val wireSizeAndDimensions = testIcon.getWireSizeAndDimensions(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- Truth.assertThat(wireSizeAndDimensions.wireSizeBytes).isEqualTo(673)
+ Truth.assertThat(wireSizeAndDimensions.wireSizeBytes).isEqualTo(547)
} else {
Truth.assertThat(wireSizeAndDimensions.wireSizeBytes).isNull()
}
@@ -71,7 +71,7 @@
val estimate = setting.estimateWireSizeInBytesAndValidateIconDimensions(context, 100, 100)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- Truth.assertThat(estimate).isEqualTo(708)
+ Truth.assertThat(estimate).isEqualTo(582)
} else {
Truth.assertThat(estimate).isEqualTo(35)
}
@@ -160,7 +160,7 @@
val estimate = setting.estimateWireSizeInBytesAndValidateIconDimensions(context, 100, 100)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- Truth.assertThat(estimate).isEqualTo(2800)
+ Truth.assertThat(estimate).isEqualTo(2296)
} else {
Truth.assertThat(estimate).isEqualTo(108)
}
@@ -182,7 +182,7 @@
val estimate = setting.estimateWireSizeInBytesAndValidateIconDimensions(context, 100, 100)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- Truth.assertThat(estimate).isEqualTo(767)
+ Truth.assertThat(estimate).isEqualTo(641)
} else {
Truth.assertThat(estimate).isEqualTo(94)
}
@@ -259,7 +259,7 @@
val estimate = setting.estimateWireSizeInBytesAndValidateIconDimensions(context, 100, 100)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- Truth.assertThat(estimate).isEqualTo(3592)
+ Truth.assertThat(estimate).isEqualTo(2962)
} else {
Truth.assertThat(estimate).isEqualTo(227)
}
diff --git a/wear/wear/src/main/java/androidx/wear/widget/drawer/WearableActionDrawerView.java b/wear/wear/src/main/java/androidx/wear/widget/drawer/WearableActionDrawerView.java
index bded2fb..cb595c4 100644
--- a/wear/wear/src/main/java/androidx/wear/widget/drawer/WearableActionDrawerView.java
+++ b/wear/wear/src/main/java/androidx/wear/widget/drawer/WearableActionDrawerView.java
@@ -401,6 +401,7 @@
}
};
+ @SuppressWarnings("UnusedVariable")
ActionListAdapter(Menu menu) {
mActionMenu = getMenu();
}
diff --git a/window/window-core/api/current.txt b/window/window-core/api/current.txt
index aa7ee82..2c8a4d2 100644
--- a/window/window-core/api/current.txt
+++ b/window/window-core/api/current.txt
@@ -8,39 +8,68 @@
package androidx.window.core.layout {
- public final class WindowHeightSizeClass {
- field public static final androidx.window.core.layout.WindowHeightSizeClass COMPACT;
- field public static final androidx.window.core.layout.WindowHeightSizeClass.Companion Companion;
- field public static final androidx.window.core.layout.WindowHeightSizeClass EXPANDED;
- field public static final androidx.window.core.layout.WindowHeightSizeClass MEDIUM;
+ @Deprecated public final class WindowHeightSizeClass {
+ field @Deprecated public static final androidx.window.core.layout.WindowHeightSizeClass COMPACT;
+ field @Deprecated public static final androidx.window.core.layout.WindowHeightSizeClass.Companion Companion;
+ field @Deprecated public static final androidx.window.core.layout.WindowHeightSizeClass EXPANDED;
+ field @Deprecated public static final androidx.window.core.layout.WindowHeightSizeClass MEDIUM;
}
- public static final class WindowHeightSizeClass.Companion {
+ @Deprecated public static final class WindowHeightSizeClass.Companion {
+ method @Deprecated public androidx.window.core.layout.WindowHeightSizeClass getCOMPACT();
+ method @Deprecated public androidx.window.core.layout.WindowHeightSizeClass getEXPANDED();
+ method @Deprecated public androidx.window.core.layout.WindowHeightSizeClass getMEDIUM();
+ property @Deprecated public androidx.window.core.layout.WindowHeightSizeClass COMPACT;
+ property @Deprecated public androidx.window.core.layout.WindowHeightSizeClass EXPANDED;
+ property @Deprecated public androidx.window.core.layout.WindowHeightSizeClass MEDIUM;
}
public final class WindowSizeClass {
- method public static androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowCoreApi public static androidx.window.core.layout.WindowSizeClass compute(int widthPx, int heightPx, float density);
- method public androidx.window.core.layout.WindowHeightSizeClass getWindowHeightSizeClass();
- method public androidx.window.core.layout.WindowWidthSizeClass getWindowWidthSizeClass();
- property public final androidx.window.core.layout.WindowHeightSizeClass windowHeightSizeClass;
- property public final androidx.window.core.layout.WindowWidthSizeClass windowWidthSizeClass;
+ ctor public WindowSizeClass(float widthDp, float heightDp);
+ ctor public WindowSizeClass(int minWidthDp, int minHeightDp);
+ method @Deprecated public static androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
+ method public int getMinHeightDp();
+ method public int getMinWidthDp();
+ method @Deprecated public androidx.window.core.layout.WindowHeightSizeClass getWindowHeightSizeClass();
+ method @Deprecated public androidx.window.core.layout.WindowWidthSizeClass getWindowWidthSizeClass();
+ method public boolean isAtLeast(int widthDp, int heightDp);
+ method public boolean isHeightAtLeast(int heightDp);
+ method public boolean isWidthAtLeast(int widthDp);
+ property public final int minHeightDp;
+ property public final int minWidthDp;
+ property @Deprecated public final androidx.window.core.layout.WindowHeightSizeClass windowHeightSizeClass;
+ property @Deprecated public final androidx.window.core.layout.WindowWidthSizeClass windowWidthSizeClass;
+ field public static final java.util.Set<androidx.window.core.layout.WindowSizeClass> BREAKPOINTS_V1;
field public static final androidx.window.core.layout.WindowSizeClass.Companion Companion;
+ field public static final int HEIGHT_DP_EXPANDED_LOWER_BOUND = 900; // 0x384
+ field public static final int HEIGHT_DP_MEDIUM_LOWER_BOUND = 480; // 0x1e0
+ field public static final int WIDTH_DP_EXPANDED_LOWER_BOUND = 840; // 0x348
+ field public static final int WIDTH_DP_MEDIUM_LOWER_BOUND = 600; // 0x258
}
public static final class WindowSizeClass.Companion {
- method public androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowCoreApi public androidx.window.core.layout.WindowSizeClass compute(int widthPx, int heightPx, float density);
+ method @Deprecated public androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
}
- public final class WindowWidthSizeClass {
- field public static final androidx.window.core.layout.WindowWidthSizeClass COMPACT;
- field public static final androidx.window.core.layout.WindowWidthSizeClass.Companion Companion;
- field public static final androidx.window.core.layout.WindowWidthSizeClass EXPANDED;
- field public static final androidx.window.core.layout.WindowWidthSizeClass MEDIUM;
+ public final class WindowSizeClassSelectors {
+ method public static androidx.window.core.layout.WindowSizeClass computeWindowSizeClass(java.util.Set<androidx.window.core.layout.WindowSizeClass>, int widthDp, int heightDp);
+ method public static androidx.window.core.layout.WindowSizeClass computeWindowSizeClassPreferHeight(java.util.Set<androidx.window.core.layout.WindowSizeClass>, int widthDp, int heightDp);
}
- public static final class WindowWidthSizeClass.Companion {
+ @Deprecated public final class WindowWidthSizeClass {
+ field @Deprecated public static final androidx.window.core.layout.WindowWidthSizeClass COMPACT;
+ field @Deprecated public static final androidx.window.core.layout.WindowWidthSizeClass.Companion Companion;
+ field @Deprecated public static final androidx.window.core.layout.WindowWidthSizeClass EXPANDED;
+ field @Deprecated public static final androidx.window.core.layout.WindowWidthSizeClass MEDIUM;
+ }
+
+ @Deprecated public static final class WindowWidthSizeClass.Companion {
+ method @Deprecated public androidx.window.core.layout.WindowWidthSizeClass getCOMPACT();
+ method @Deprecated public androidx.window.core.layout.WindowWidthSizeClass getEXPANDED();
+ method @Deprecated public androidx.window.core.layout.WindowWidthSizeClass getMEDIUM();
+ property @Deprecated public androidx.window.core.layout.WindowWidthSizeClass COMPACT;
+ property @Deprecated public androidx.window.core.layout.WindowWidthSizeClass EXPANDED;
+ property @Deprecated public androidx.window.core.layout.WindowWidthSizeClass MEDIUM;
}
}
diff --git a/window/window-core/api/restricted_current.txt b/window/window-core/api/restricted_current.txt
index aa7ee82..2c8a4d2 100644
--- a/window/window-core/api/restricted_current.txt
+++ b/window/window-core/api/restricted_current.txt
@@ -8,39 +8,68 @@
package androidx.window.core.layout {
- public final class WindowHeightSizeClass {
- field public static final androidx.window.core.layout.WindowHeightSizeClass COMPACT;
- field public static final androidx.window.core.layout.WindowHeightSizeClass.Companion Companion;
- field public static final androidx.window.core.layout.WindowHeightSizeClass EXPANDED;
- field public static final androidx.window.core.layout.WindowHeightSizeClass MEDIUM;
+ @Deprecated public final class WindowHeightSizeClass {
+ field @Deprecated public static final androidx.window.core.layout.WindowHeightSizeClass COMPACT;
+ field @Deprecated public static final androidx.window.core.layout.WindowHeightSizeClass.Companion Companion;
+ field @Deprecated public static final androidx.window.core.layout.WindowHeightSizeClass EXPANDED;
+ field @Deprecated public static final androidx.window.core.layout.WindowHeightSizeClass MEDIUM;
}
- public static final class WindowHeightSizeClass.Companion {
+ @Deprecated public static final class WindowHeightSizeClass.Companion {
+ method @Deprecated public androidx.window.core.layout.WindowHeightSizeClass getCOMPACT();
+ method @Deprecated public androidx.window.core.layout.WindowHeightSizeClass getEXPANDED();
+ method @Deprecated public androidx.window.core.layout.WindowHeightSizeClass getMEDIUM();
+ property @Deprecated public androidx.window.core.layout.WindowHeightSizeClass COMPACT;
+ property @Deprecated public androidx.window.core.layout.WindowHeightSizeClass EXPANDED;
+ property @Deprecated public androidx.window.core.layout.WindowHeightSizeClass MEDIUM;
}
public final class WindowSizeClass {
- method public static androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowCoreApi public static androidx.window.core.layout.WindowSizeClass compute(int widthPx, int heightPx, float density);
- method public androidx.window.core.layout.WindowHeightSizeClass getWindowHeightSizeClass();
- method public androidx.window.core.layout.WindowWidthSizeClass getWindowWidthSizeClass();
- property public final androidx.window.core.layout.WindowHeightSizeClass windowHeightSizeClass;
- property public final androidx.window.core.layout.WindowWidthSizeClass windowWidthSizeClass;
+ ctor public WindowSizeClass(float widthDp, float heightDp);
+ ctor public WindowSizeClass(int minWidthDp, int minHeightDp);
+ method @Deprecated public static androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
+ method public int getMinHeightDp();
+ method public int getMinWidthDp();
+ method @Deprecated public androidx.window.core.layout.WindowHeightSizeClass getWindowHeightSizeClass();
+ method @Deprecated public androidx.window.core.layout.WindowWidthSizeClass getWindowWidthSizeClass();
+ method public boolean isAtLeast(int widthDp, int heightDp);
+ method public boolean isHeightAtLeast(int heightDp);
+ method public boolean isWidthAtLeast(int widthDp);
+ property public final int minHeightDp;
+ property public final int minWidthDp;
+ property @Deprecated public final androidx.window.core.layout.WindowHeightSizeClass windowHeightSizeClass;
+ property @Deprecated public final androidx.window.core.layout.WindowWidthSizeClass windowWidthSizeClass;
+ field public static final java.util.Set<androidx.window.core.layout.WindowSizeClass> BREAKPOINTS_V1;
field public static final androidx.window.core.layout.WindowSizeClass.Companion Companion;
+ field public static final int HEIGHT_DP_EXPANDED_LOWER_BOUND = 900; // 0x384
+ field public static final int HEIGHT_DP_MEDIUM_LOWER_BOUND = 480; // 0x1e0
+ field public static final int WIDTH_DP_EXPANDED_LOWER_BOUND = 840; // 0x348
+ field public static final int WIDTH_DP_MEDIUM_LOWER_BOUND = 600; // 0x258
}
public static final class WindowSizeClass.Companion {
- method public androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowCoreApi public androidx.window.core.layout.WindowSizeClass compute(int widthPx, int heightPx, float density);
+ method @Deprecated public androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
}
- public final class WindowWidthSizeClass {
- field public static final androidx.window.core.layout.WindowWidthSizeClass COMPACT;
- field public static final androidx.window.core.layout.WindowWidthSizeClass.Companion Companion;
- field public static final androidx.window.core.layout.WindowWidthSizeClass EXPANDED;
- field public static final androidx.window.core.layout.WindowWidthSizeClass MEDIUM;
+ public final class WindowSizeClassSelectors {
+ method public static androidx.window.core.layout.WindowSizeClass computeWindowSizeClass(java.util.Set<androidx.window.core.layout.WindowSizeClass>, int widthDp, int heightDp);
+ method public static androidx.window.core.layout.WindowSizeClass computeWindowSizeClassPreferHeight(java.util.Set<androidx.window.core.layout.WindowSizeClass>, int widthDp, int heightDp);
}
- public static final class WindowWidthSizeClass.Companion {
+ @Deprecated public final class WindowWidthSizeClass {
+ field @Deprecated public static final androidx.window.core.layout.WindowWidthSizeClass COMPACT;
+ field @Deprecated public static final androidx.window.core.layout.WindowWidthSizeClass.Companion Companion;
+ field @Deprecated public static final androidx.window.core.layout.WindowWidthSizeClass EXPANDED;
+ field @Deprecated public static final androidx.window.core.layout.WindowWidthSizeClass MEDIUM;
+ }
+
+ @Deprecated public static final class WindowWidthSizeClass.Companion {
+ method @Deprecated public androidx.window.core.layout.WindowWidthSizeClass getCOMPACT();
+ method @Deprecated public androidx.window.core.layout.WindowWidthSizeClass getEXPANDED();
+ method @Deprecated public androidx.window.core.layout.WindowWidthSizeClass getMEDIUM();
+ property @Deprecated public androidx.window.core.layout.WindowWidthSizeClass COMPACT;
+ property @Deprecated public androidx.window.core.layout.WindowWidthSizeClass EXPANDED;
+ property @Deprecated public androidx.window.core.layout.WindowWidthSizeClass MEDIUM;
}
}
diff --git a/window/window-core/build.gradle b/window/window-core/build.gradle
index a484af0..926941d 100644
--- a/window/window-core/build.gradle
+++ b/window/window-core/build.gradle
@@ -26,12 +26,18 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
androidXMultiplatform {
jvm()
- android()
+ androidLibrary {
+ namespace = "androidx.window.core"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ }
mac()
linux()
ios()
@@ -69,10 +75,6 @@
enableBinaryCompatibilityValidator = false
}
-android {
- namespace "androidx.window.core"
-}
-
androidx {
name = "WindowManager Core"
type = LibraryType.PUBLISHED_LIBRARY
diff --git a/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowHeightSizeClass.kt b/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowHeightSizeClass.kt
index e69a63f..1eccac7 100644
--- a/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowHeightSizeClass.kt
+++ b/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowHeightSizeClass.kt
@@ -16,9 +16,6 @@
package androidx.window.core.layout
-import androidx.window.core.layout.WindowHeightSizeClass.Companion.COMPACT
-import androidx.window.core.layout.WindowHeightSizeClass.Companion.EXPANDED
-import androidx.window.core.layout.WindowHeightSizeClass.Companion.MEDIUM
import kotlin.jvm.JvmField
/**
@@ -27,6 +24,8 @@
* type. It is possible to have resizeable windows in different device types. The viewport might
* change from a [COMPACT] all the way to an [EXPANDED] size class.
*/
+@Suppress("DEPRECATION")
+@Deprecated("WindowHeightSizeClass will not be developed further, use WindowSizeClass instead.")
class WindowHeightSizeClass private constructor(private val rawValue: Int) {
override fun toString(): String {
@@ -56,16 +55,22 @@
companion object {
/** A bucket to represent a compact height, typical for a phone that is in landscape. */
- @JvmField val COMPACT: WindowHeightSizeClass = WindowHeightSizeClass(0)
+ @Deprecated("WindowHeightSizeClass not be developed further.")
+ @JvmField
+ val COMPACT: WindowHeightSizeClass = WindowHeightSizeClass(0)
/** A bucket to represent a medium height, typical for a phone in portrait or a tablet. */
- @JvmField val MEDIUM: WindowHeightSizeClass = WindowHeightSizeClass(1)
+ @Deprecated("WindowHeightSizeClass not be developed further.")
+ @JvmField
+ val MEDIUM: WindowHeightSizeClass = WindowHeightSizeClass(1)
/**
* A bucket to represent an expanded height window, typical for a large tablet or a desktop
* form-factor.
*/
- @JvmField val EXPANDED: WindowHeightSizeClass = WindowHeightSizeClass(2)
+ @Deprecated("WindowHeightSizeClass not be developed further.")
+ @JvmField
+ val EXPANDED: WindowHeightSizeClass = WindowHeightSizeClass(2)
/**
* Returns a recommended [WindowHeightSizeClass] for the height of a window given the height
@@ -75,6 +80,7 @@
* @return A recommended size class for the height
* @throws IllegalArgumentException if the height is negative
*/
+ @Deprecated("WindowHeightSizeClass not be developed further.")
internal fun compute(dpHeight: Float): WindowHeightSizeClass {
require(dpHeight >= 0) { "Height must be positive, received $dpHeight" }
return when {
diff --git a/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowSizeClass.kt b/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowSizeClass.kt
index 9df0361..43bef20 100644
--- a/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowSizeClass.kt
+++ b/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowSizeClass.kt
@@ -16,30 +16,28 @@
package androidx.window.core.layout
-import androidx.window.core.ExperimentalWindowCoreApi
+import kotlin.jvm.JvmField
import kotlin.jvm.JvmStatic
/**
- * [WindowSizeClass] represents breakpoints for a viewport. The recommended width and height break
- * points are presented through [windowWidthSizeClass] and [windowHeightSizeClass]. Designers should
- * design around the different combinations of width and height buckets. Developers should use the
- * different buckets to specify the layouts. Ideally apps will work well in each bucket and by
- * extension work well across multiple devices. If two devices are in similar buckets they should
- * behave similarly.
+ * [WindowSizeClass] represents breakpoints for a viewport. Designers should design around the
+ * different combinations of width and height buckets. Developers should use the different buckets
+ * to specify the layouts. Ideally apps will work well in each bucket and by extension work well
+ * across multiple devices. If two devices are in similar buckets they should behave similarly.
*
* This class is meant to be a common definition that can be shared across different device types.
- * Application developers can use WindowSizeClass to have standard window buckets and design the UI
- * around those buckets. Library developers can use these buckets to create different UI with
+ * Application developers can use [WindowSizeClass] to have standard window buckets and design the
+ * UI around those buckets. Library developers can use these buckets to create different UI with
* respect to each bucket. This will help with consistency across multiple device types.
*
* A library developer use-case can be creating some navigation UI library. For a size class with
- * the [WindowWidthSizeClass.EXPANDED] width it might be more reasonable to have a side navigation.
- * For a [WindowWidthSizeClass.COMPACT] width, a bottom navigation might be a better fit.
+ * the [WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND] width it might be more reasonable to have a
+ * side navigation.
*
* An application use-case can be applied for apps that use a list-detail pattern. The app can use
- * the [WindowWidthSizeClass.MEDIUM] to determine if there is enough space to show the list and the
- * detail side by side. If all apps follow this guidance then it will present a very consistent user
- * experience.
+ * the [WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND] to determine if there is enough space to show
+ * the list and the detail side by side. If all apps follow this guidance then it will present a
+ * very consistent user experience.
*
* In some cases developers or UI systems may decide to create their own break points. A developer
* might optimize for a window that is smaller than the supported break points or larger. A UI
@@ -50,13 +48,58 @@
* @see WindowWidthSizeClass
* @see WindowHeightSizeClass
*/
-class WindowSizeClass
-private constructor(
+class WindowSizeClass(
+ /** Returns the lower bound for the width of the size class in dp. */
+ val minWidthDp: Int,
+ /** Returns the lower bound for the height of the size class in dp. */
+ val minHeightDp: Int
+) {
+
+ /** A convenience constructor that will truncate to ints. */
+ constructor(widthDp: Float, heightDp: Float) : this(widthDp.toInt(), heightDp.toInt())
+
+ init {
+ require(minWidthDp >= 0) {
+ "Expected minWidthDp to be at least 0, minWidthDp: $minWidthDp."
+ }
+ require(minHeightDp >= 0) {
+ "Expected minHeightDp to be at least 0, minHeightDp: $minHeightDp."
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ @Deprecated("Use either isWidthAtLeast or isAtLeast to check matching bounds.")
/** Returns the [WindowWidthSizeClass] that corresponds to the widthDp of the window. */
- val windowWidthSizeClass: WindowWidthSizeClass,
+ val windowWidthSizeClass: WindowWidthSizeClass
+ get() = WindowWidthSizeClass.compute(minWidthDp.toFloat())
+
+ @Suppress("DEPRECATION")
+ @Deprecated("Use either isHeightAtLeast or isAtLeast to check matching bounds.")
/** Returns the [WindowHeightSizeClass] that corresponds to the heightDp of the window. */
val windowHeightSizeClass: WindowHeightSizeClass
-) {
+ get() = WindowHeightSizeClass.compute(minHeightDp.toFloat())
+
+ /**
+ * Returns `true` when [widthDp] is greater than or equal to [minWidthDp], `false` otherwise.
+ */
+ fun isWidthAtLeast(widthDp: Int): Boolean {
+ return widthDp >= minWidthDp
+ }
+
+ /**
+ * Returns `true` when [heightDp] is greater than or equal to [minHeightDp], `false` otherwise.
+ */
+ fun isHeightAtLeast(heightDp: Int): Boolean {
+ return heightDp >= minHeightDp
+ }
+
+ /**
+ * Returns `true` when [widthDp] is greater than or equal to [minWidthDp] and [heightDp] is
+ * greater than or equal to [minHeightDp], `false` otherwise.
+ */
+ fun isAtLeast(widthDp: Int, heightDp: Int): Boolean {
+ return isWidthAtLeast(widthDp) && isHeightAtLeast(heightDp)
+ }
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -64,25 +107,49 @@
other as WindowSizeClass
- if (windowWidthSizeClass != other.windowWidthSizeClass) return false
- if (windowHeightSizeClass != other.windowHeightSizeClass) return false
+ if (minWidthDp != other.minWidthDp) return false
+ if (minHeightDp != other.minHeightDp) return false
return true
}
override fun hashCode(): Int {
- var result = windowWidthSizeClass.hashCode()
- result = 31 * result + windowHeightSizeClass.hashCode()
+ var result = minWidthDp
+ result = 31 * result + minHeightDp
return result
}
override fun toString(): String {
- return "WindowSizeClass {" +
- "windowWidthSizeClass=$windowWidthSizeClass, " +
- "windowHeightSizeClass=$windowHeightSizeClass }"
+ return "WindowSizeClass(minWidthDp=$minWidthDp, minHeightDp=$minHeightDp)"
}
companion object {
+ /** A lower bound for a size class with Medium width in dp. */
+ const val WIDTH_DP_MEDIUM_LOWER_BOUND = 600
+
+ /** A lower bound for a size class with Expanded width in dp. */
+ const val WIDTH_DP_EXPANDED_LOWER_BOUND = 840
+
+ /** A lower bound for a size class with Medium height in dp. */
+ const val HEIGHT_DP_MEDIUM_LOWER_BOUND = 480
+
+ /** A lower bound for a size class with Expanded height in dp. */
+ const val HEIGHT_DP_EXPANDED_LOWER_BOUND = 900
+
+ private val WIDTH_DP_BREAKPOINTS_V1 =
+ listOf(WIDTH_DP_MEDIUM_LOWER_BOUND, WIDTH_DP_EXPANDED_LOWER_BOUND)
+
+ private val HEIGHT_DP_BREAKPOINTS_V1 =
+ listOf(HEIGHT_DP_MEDIUM_LOWER_BOUND, HEIGHT_DP_EXPANDED_LOWER_BOUND)
+
+ @JvmField
+ val BREAKPOINTS_V1 =
+ WIDTH_DP_BREAKPOINTS_V1.flatMap { widthBp ->
+ HEIGHT_DP_BREAKPOINTS_V1.map { heightBp ->
+ WindowSizeClass(minWidthDp = widthBp, minHeightDp = heightBp)
+ }
+ }
+ .toSet()
/**
* Computes the recommended [WindowSizeClass] for the given width and height in DP.
@@ -93,28 +160,21 @@
* @throws IllegalArgumentException if [dpWidth] or [dpHeight] is negative.
*/
@JvmStatic
+ @Deprecated("Use the constructor instead.")
fun compute(dpWidth: Float, dpHeight: Float): WindowSizeClass {
- return WindowSizeClass(
- WindowWidthSizeClass.compute(dpWidth),
- WindowHeightSizeClass.compute(dpHeight)
- )
- }
-
- /**
- * Computes the [WindowSizeClass] for the given width and height in pixels with density.
- *
- * @param widthPx width of a window in PX.
- * @param heightPx height of a window in PX.
- * @param density density of the display where the window is shown.
- * @return [WindowSizeClass] that is recommended for the given dimensions.
- * @throws IllegalArgumentException if [widthPx], [heightPx], or [density] is negative.
- */
- @JvmStatic
- @ExperimentalWindowCoreApi
- fun compute(widthPx: Int, heightPx: Int, density: Float): WindowSizeClass {
- val widthDp = widthPx / density
- val heightDp = heightPx / density
- return compute(widthDp, heightDp)
+ val widthDp =
+ when {
+ dpWidth >= WIDTH_DP_EXPANDED_LOWER_BOUND -> WIDTH_DP_EXPANDED_LOWER_BOUND
+ dpWidth >= WIDTH_DP_MEDIUM_LOWER_BOUND -> WIDTH_DP_MEDIUM_LOWER_BOUND
+ else -> 0
+ }
+ val heightDp =
+ when {
+ dpHeight >= HEIGHT_DP_EXPANDED_LOWER_BOUND -> HEIGHT_DP_EXPANDED_LOWER_BOUND
+ dpHeight >= HEIGHT_DP_MEDIUM_LOWER_BOUND -> HEIGHT_DP_MEDIUM_LOWER_BOUND
+ else -> 0
+ }
+ return WindowSizeClass(widthDp, heightDp)
}
}
}
diff --git a/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowSizeClassSelectors.kt b/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowSizeClassSelectors.kt
new file mode 100644
index 0000000..25ef7c0
--- /dev/null
+++ b/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowSizeClassSelectors.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("WindowSizeClassSelectors")
+
+package androidx.window.core.layout
+
+import kotlin.jvm.JvmName
+
+/**
+ * Returns the largest [WindowSizeClass] that is within the bounds of ([widthDp], [heightDp]). This
+ * method prefers width and uses max height to break ties. If there is no match a default of
+ * `WindowSizeClass(0,0)` is returned. Examples: Input: Set: `setOf(WindowSizeClass(300, 300),
+ * WindowSizeClass(300, 600)` widthDp: `300` heightDp: `800` Output: `WindowSizeClass(300, 600)`
+ * Input: Set: `setOf(WindowSizeClass(300, 300), WindowSizeClass(300, 600)` widthDp: `300` heightDp:
+ * `400` Output: `WindowSizeClass(300, 300)`
+ *
+ * @param widthDp the width of the window to match a [WindowSizeClass] to.
+ * @param heightDp the height of the window to match a [WindowSizeClass] to.
+ */
+fun Set<WindowSizeClass>.computeWindowSizeClass(widthDp: Int, heightDp: Int): WindowSizeClass {
+ var maxWidth = 0
+ forEach { bucket ->
+ if (bucket.minWidthDp <= widthDp && bucket.minWidthDp > maxWidth) {
+ maxWidth = bucket.minWidthDp
+ }
+ }
+ var match = WindowSizeClass(0, 0)
+ forEach { bucket ->
+ if (
+ bucket.minWidthDp == maxWidth &&
+ bucket.minHeightDp <= heightDp &&
+ match.minHeightDp < bucket.minHeightDp
+ ) {
+ match = bucket
+ }
+ }
+ return match
+}
+
+/**
+ * Returns the largest [WindowSizeClass] that is within the bounds of ([widthDp], [heightDp]). This
+ * method prefers height and uses max width to break ties. If there is no match a default of
+ * `WindowSizeClass(0,0)` is returned. Examples: Input: Set: `setOf(WindowSizeClass(300, 300),
+ * WindowSizeClass(600, 300)` widthDp: `800` heightDp: `300` Output: `WindowSizeClass(600, 300)`
+ * Input: Set: `setOf(WindowSizeClass(300, 300), WindowSizeClass(600, 300)` widthDp: `400` heightDp:
+ * `300` Output: `WindowSizeClass(300, 300)`
+ *
+ * @param widthDp the width of the window to match a [WindowSizeClass] to.
+ * @param heightDp the height of the window to match a [WindowSizeClass] to.
+ */
+fun Set<WindowSizeClass>.computeWindowSizeClassPreferHeight(
+ widthDp: Int,
+ heightDp: Int
+): WindowSizeClass {
+ var maxHeight = 0
+ forEach { bucket ->
+ if (bucket.minHeightDp <= heightDp && bucket.minHeightDp > maxHeight) {
+ maxHeight = bucket.minHeightDp
+ }
+ }
+ var match = WindowSizeClass(0, 0)
+ forEach { bucket ->
+ if (
+ bucket.minHeightDp == maxHeight &&
+ bucket.minWidthDp <= widthDp &&
+ match.minWidthDp < bucket.minWidthDp
+ ) {
+ match = bucket
+ }
+ }
+ return match
+}
diff --git a/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowWidthSizeClass.kt b/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowWidthSizeClass.kt
index 11af3a0..3097d8f 100644
--- a/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowWidthSizeClass.kt
+++ b/window/window-core/src/commonMain/kotlin/androidx/window/core/layout/WindowWidthSizeClass.kt
@@ -24,7 +24,10 @@
* type. It is possible to have resizeable windows in different device types. The viewport might
* change from a [COMPACT] all the way to an [EXPANDED] size class.
*/
+@Suppress("DEPRECATION")
+@Deprecated("WindowWidthSizeClass will not be developed further, use WindowSizeClass instead.")
class WindowWidthSizeClass private constructor(private val rawValue: Int) {
+
override fun toString(): String {
val name =
when (this) {
@@ -52,19 +55,25 @@
companion object {
/** A bucket to represent a compact width window, typical for a phone in portrait. */
- @JvmField val COMPACT: WindowWidthSizeClass = WindowWidthSizeClass(0)
+ @Deprecated("WindowWidthSizeClass not be developed further.")
+ @JvmField
+ val COMPACT: WindowWidthSizeClass = WindowWidthSizeClass(0)
/**
* A bucket to represent a medium width window, typical for a phone in landscape or a
* tablet.
*/
- @JvmField val MEDIUM: WindowWidthSizeClass = WindowWidthSizeClass(1)
+ @Deprecated("WindowWidthSizeClass not be developed further.")
+ @JvmField
+ val MEDIUM: WindowWidthSizeClass = WindowWidthSizeClass(1)
/**
* A bucket to represent an expanded width window, typical for a large tablet or desktop
* form-factor.
*/
- @JvmField val EXPANDED: WindowWidthSizeClass = WindowWidthSizeClass(2)
+ @Deprecated("WindowWidthSizeClass not be developed further.")
+ @JvmField
+ val EXPANDED: WindowWidthSizeClass = WindowWidthSizeClass(2)
/**
* Returns a recommended [WindowWidthSizeClass] for the width of a window given the width in
@@ -74,6 +83,7 @@
* @return A recommended size class for the width
* @throws IllegalArgumentException if the width is negative
*/
+ @Deprecated("WindowWidthSizeClass not be developed further.")
internal fun compute(dpWidth: Float): WindowWidthSizeClass {
require(dpWidth >= 0) { "Width must be positive, received $dpWidth" }
return when {
diff --git a/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowHeightSizeClassTest.kt b/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowHeightSizeClassTest.kt
index 80868cf..7183670 100644
--- a/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowHeightSizeClassTest.kt
+++ b/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowHeightSizeClassTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("DEPRECATION")
+
package androidx.window.core.layout
import androidx.window.core.layout.WindowHeightSizeClass.Companion.COMPACT
diff --git a/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowSizeClassSelectorsTest.kt b/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowSizeClassSelectorsTest.kt
new file mode 100644
index 0000000..f72e378
--- /dev/null
+++ b/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowSizeClassSelectorsTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.core.layout
+
+import androidx.window.core.layout.WindowSizeClass.Companion.HEIGHT_DP_MEDIUM_LOWER_BOUND
+import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_MEDIUM_LOWER_BOUND
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class WindowSizeClassSelectorsTest {
+
+ val coreSet = WindowSizeClass.BREAKPOINTS_V1
+
+ @Test
+ fun compute_window_size_class_returns_zero_for_default() {
+ // coreSet does not contain 10, 10
+ val actual = coreSet.computeWindowSizeClass(10, 10)
+
+ assertEquals(WindowSizeClass(0, 0), actual)
+ }
+
+ @Test
+ fun compute_window_size_class_returns_exact_match() {
+ val expected = WindowSizeClass(WIDTH_DP_MEDIUM_LOWER_BOUND, HEIGHT_DP_MEDIUM_LOWER_BOUND)
+
+ // coreSet contains WindowSizeClass(MEDIUM, MEDIUM)
+ val actual =
+ coreSet.computeWindowSizeClass(
+ WIDTH_DP_MEDIUM_LOWER_BOUND,
+ HEIGHT_DP_MEDIUM_LOWER_BOUND
+ )
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun compute_window_size_class_returns_bounded_match() {
+ val expected = WindowSizeClass(WIDTH_DP_MEDIUM_LOWER_BOUND, HEIGHT_DP_MEDIUM_LOWER_BOUND)
+
+ // coreSet contains WindowSizeClass(MEDIUM, MEDIUM)
+ val actual =
+ coreSet.computeWindowSizeClass(
+ WIDTH_DP_MEDIUM_LOWER_BOUND + 1,
+ HEIGHT_DP_MEDIUM_LOWER_BOUND + 1
+ )
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun compute_window_size_class_prefers_width() {
+ val expected = WindowSizeClass(minWidthDp = 100, minHeightDp = 50)
+
+ val actual =
+ setOf(
+ WindowSizeClass(minWidthDp = 100, minHeightDp = 50),
+ WindowSizeClass(minWidthDp = 50, minHeightDp = 100)
+ )
+ .computeWindowSizeClass(100, 100)
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun compute_window_size_class_breaks_tie_with_height() {
+ val expected = WindowSizeClass(minWidthDp = 100, minHeightDp = 100)
+
+ val actual =
+ setOf(
+ WindowSizeClass(minWidthDp = 100, minHeightDp = 50),
+ WindowSizeClass(minWidthDp = 100, minHeightDp = 100)
+ )
+ .computeWindowSizeClass(200, 200)
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun compute_window_size_class_preferring_height_returns_zero_for_default() {
+ // coreSet does not contain 10, 10
+ val actual = coreSet.computeWindowSizeClassPreferHeight(10, 10)
+
+ assertEquals(WindowSizeClass(0, 0), actual)
+ }
+
+ @Test
+ fun compute_window_size_class_preferring_height_returns_exact_match() {
+ val expected = WindowSizeClass(WIDTH_DP_MEDIUM_LOWER_BOUND, HEIGHT_DP_MEDIUM_LOWER_BOUND)
+
+ // coreSet contains WindowSizeClass(MEDIUM, MEDIUM)
+ val actual =
+ coreSet.computeWindowSizeClassPreferHeight(
+ WIDTH_DP_MEDIUM_LOWER_BOUND,
+ HEIGHT_DP_MEDIUM_LOWER_BOUND
+ )
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun compute_window_size_class_preferring_height_returns_bounded_match() {
+ val expected = WindowSizeClass(WIDTH_DP_MEDIUM_LOWER_BOUND, HEIGHT_DP_MEDIUM_LOWER_BOUND)
+
+ // coreSet contains WindowSizeClass(MEDIUM, MEDIUM)
+ val actual =
+ coreSet.computeWindowSizeClassPreferHeight(
+ WIDTH_DP_MEDIUM_LOWER_BOUND + 1,
+ HEIGHT_DP_MEDIUM_LOWER_BOUND + 1
+ )
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun compute_window_size_class_preferring_height_prefers_height() {
+ val expected = WindowSizeClass(minWidthDp = 50, minHeightDp = 100)
+
+ val actual =
+ setOf(
+ WindowSizeClass(minWidthDp = 100, minHeightDp = 50),
+ WindowSizeClass(minWidthDp = 50, minHeightDp = 100)
+ )
+ .computeWindowSizeClassPreferHeight(100, 100)
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun compute_window_size_class_preferring_height_breaks_tie_with_width() {
+ val expected = WindowSizeClass(minWidthDp = 100, minHeightDp = 100)
+
+ val actual =
+ setOf(
+ WindowSizeClass(minWidthDp = 50, minHeightDp = 100),
+ WindowSizeClass(minWidthDp = 100, minHeightDp = 100)
+ )
+ .computeWindowSizeClass(200, 200)
+
+ assertEquals(expected, actual)
+ }
+}
diff --git a/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowSizeClassTest.kt b/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowSizeClassTest.kt
index 46607ab..816e40c 100644
--- a/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowSizeClassTest.kt
+++ b/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowSizeClassTest.kt
@@ -16,16 +16,18 @@
package androidx.window.core.layout
-import androidx.window.core.ExperimentalWindowCoreApi
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
/** Tests for [WindowSizeClass] that verify construction. */
class WindowSizeClassTest {
+ @Suppress("DEPRECATION")
@Test
- fun testWidthSizeClass_construction() {
+ fun testWindowWidthSizeClass_compatibility() {
val expected =
listOf(
WindowWidthSizeClass.COMPACT,
@@ -41,6 +43,7 @@
assertEquals(expected, actual)
}
+ @Suppress("DEPRECATION")
@Test
fun testWindowSizeClass_computeRounds() {
val expected = WindowSizeClass.compute(0f, 0f)
@@ -50,18 +53,9 @@
assertEquals(expected, actual)
}
- @OptIn(ExperimentalWindowCoreApi::class)
+ @Suppress("DEPRECATION")
@Test
- fun testConstruction_usingPx() {
- val expected = WindowSizeClass.compute(600f, 600f)
-
- val actual = WindowSizeClass.compute(600, 600, 1f)
-
- assertEquals(expected, actual)
- }
-
- @Test
- fun testHeightSizeClass_construction() {
+ fun testWindowHeightSizeClass_compatibility() {
val expected =
listOf(
WindowHeightSizeClass.COMPACT,
@@ -79,16 +73,17 @@
@Test
fun testEqualsImpliesHashCode() {
- val first = WindowSizeClass.compute(100f, 500f)
- val second = WindowSizeClass.compute(100f, 500f)
+ val first = WindowSizeClass(100, 500)
+ val second = WindowSizeClass(100, 500)
assertEquals(first, second)
assertEquals(first.hashCode(), second.hashCode())
}
+ @Suppress("DEPRECATION")
@Test
fun truncated_float_does_not_throw() {
- val sizeClass = WindowSizeClass.compute(0.5f, 0.5f)
+ val sizeClass = WindowSizeClass(0.5f, 0.5f)
val widthSizeClass = sizeClass.windowWidthSizeClass
val heightSizeClass = sizeClass.windowHeightSizeClass
@@ -97,9 +92,10 @@
assertEquals(WindowHeightSizeClass.COMPACT, heightSizeClass)
}
+ @Suppress("DEPRECATION")
@Test
fun zero_size_class_does_not_throw() {
- val sizeClass = WindowSizeClass.compute(0f, 0f)
+ val sizeClass = WindowSizeClass(0, 0)
val widthSizeClass = sizeClass.windowWidthSizeClass
val heightSizeClass = sizeClass.windowHeightSizeClass
@@ -110,11 +106,94 @@
@Test
fun negative_width_throws() {
- assertFailsWith(IllegalArgumentException::class) { WindowSizeClass.compute(-1f, 0f) }
+ assertFailsWith(IllegalArgumentException::class) { WindowSizeClass(-1, 0) }
}
@Test
fun negative_height_throws() {
- assertFailsWith(IllegalArgumentException::class) { WindowSizeClass.compute(0f, -1f) }
+ assertFailsWith(IllegalArgumentException::class) { WindowSizeClass(0, -1) }
+ }
+
+ @Test
+ fun is_width_at_least_returns_true_when_input_is_greater() {
+ val width = 200
+ val height = 100
+ val sizeClass = WindowSizeClass(width, height)
+
+ assertTrue(sizeClass.isWidthAtLeast(width + 1))
+ }
+
+ @Test
+ fun is_width_at_least_returns_true_when_input_is_equal() {
+ val width = 200
+ val height = 100
+ val sizeClass = WindowSizeClass(width, height)
+
+ assertTrue(sizeClass.isWidthAtLeast(width))
+ }
+
+ @Test
+ fun is_width_at_least_returns_false_when_input_is_smaller() {
+ val width = 200
+ val height = 100
+ val sizeClass = WindowSizeClass(width, height)
+
+ assertFalse(sizeClass.isWidthAtLeast(width - 1))
+ }
+
+ @Test
+ fun is_height_at_least_returns_true_when_input_is_greater() {
+ val width = 200
+ val height = 100
+ val sizeClass = WindowSizeClass(width, height)
+
+ assertTrue(sizeClass.isHeightAtLeast(height + 1))
+ }
+
+ @Test
+ fun is_height_at_least_returns_true_when_input_is_equal() {
+ val width = 200
+ val height = 100
+ val sizeClass = WindowSizeClass(width, height)
+
+ assertTrue(sizeClass.isHeightAtLeast(height))
+ }
+
+ @Test
+ fun is_height_at_least_returns_false_when_input_is_smaller() {
+ val width = 200
+ val height = 100
+ val sizeClass = WindowSizeClass(width, height)
+
+ assertFalse(sizeClass.isHeightAtLeast(height - 1))
+ }
+
+ @Test
+ fun is_at_least_returns_true_when_input_is_greater() {
+ val width = 200
+ val height = 100
+ val sizeClass = WindowSizeClass(width, height)
+
+ assertTrue(sizeClass.isAtLeast(width, height + 1))
+ assertTrue(sizeClass.isAtLeast(width + 1, height))
+ }
+
+ @Test
+ fun is_at_least_returns_true_when_input_is_equal() {
+ val width = 200
+ val height = 100
+ val sizeClass = WindowSizeClass(width, height)
+
+ assertTrue(sizeClass.isAtLeast(width, height))
+ }
+
+ @Test
+ fun is_at_least_returns_false_when_input_is_smaller() {
+ val width = 200
+ val height = 100
+ val sizeClass = WindowSizeClass(width, height)
+
+ assertFalse(sizeClass.isAtLeast(width, height - 1))
+ assertFalse(sizeClass.isAtLeast(width - 1, height))
}
}
diff --git a/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowWidthSizeClassTest.kt b/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowWidthSizeClassTest.kt
index 85ab99b..520b9bf 100644
--- a/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowWidthSizeClassTest.kt
+++ b/window/window-core/src/commonTest/kotlin/androidx/window/core/layout/WindowWidthSizeClassTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("DEPRECATION")
+
package androidx.window.core.layout
import androidx.window.core.layout.WindowWidthSizeClass.Companion.COMPACT
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/coresdk/WindowStateScreen.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/coresdk/WindowStateScreen.kt
index 92e85eb..3ff2766 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/coresdk/WindowStateScreen.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/coresdk/WindowStateScreen.kt
@@ -154,7 +154,7 @@
activityDisplayBounds = Rect(0, 0, 960, 2142),
),
)
- DemoTheme { WindowStateScreen(viewModel = WindowStateViewModel(windowStates)) }
+ DemoTheme { WindowStateScreen(viewModel = viewModel { WindowStateViewModel(windowStates) }) }
}
/**
diff --git a/window/window/build.gradle b/window/window/build.gradle
index 5707f86..004b04a 100644
--- a/window/window/build.gradle
+++ b/window/window/build.gradle
@@ -54,14 +54,14 @@
implementation("androidx.core:core:1.8.0")
def extensions_core_version = "androidx.window.extensions.core:core:1.0.0"
- def extensions_version = "androidx.window.extensions:extensions:1.4.0-alpha01"
- // A compile only dependency on extnensions.core so that other libraries do not expose it
+ def extensions_version = "androidx.window.extensions:extensions:1.4.0-beta01"
+ // A compile only dependency on extensions.core so that other libraries do not expose it
// transitively.
compileOnly(extensions_core_version)
// Test implementation is required since extensions:core is on device. So it is required to
// import it in some form. For the library it will be available on device.
testImplementation(extensions_core_version)
- // A compile only dependency on extnensions.core so that other libraries do not expose it
+ // A compile only dependency on extensions.core so that other libraries do not expose it
// transitively. The androidTestCompile is added because tests are not getting the dependency
// transitively.
androidTestCompileOnly(extensions_core_version)