Merge "Expand nullness lint check to more type-use positions" into androidx-main
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-ktx/api/1.10.0-beta01.txt b/activity/activity-ktx/api/1.10.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/activity/activity-ktx/api/1.10.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
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/res-1.10.0-beta01.txt b/activity/activity/api/res-1.10.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/activity/activity/api/res-1.10.0-beta01.txt
+++ /dev/null
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/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/AssertsAssert.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/AssertsAssert.kt
new file mode 100644
index 0000000..81d31ba
--- /dev/null
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/AssertsAssert.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.benchmark
+
+import kotlin.test.Test
+import org.junit.Assert
+
+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-macro/build.gradle b/benchmark/benchmark-macro/build.gradle
index 3e00f6b..23786dd 100644
--- a/benchmark/benchmark-macro/build.gradle
+++ b/benchmark/benchmark-macro/build.gradle
@@ -166,7 +166,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)
}
}
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/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/KLibDumpParser.kt b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/KLibDumpParser.kt
index 41335c3..be77a52 100644
--- a/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/KLibDumpParser.kt
+++ b/binarycompatibilityvalidator/binarycompatibilityvalidator/src/main/java/androidx/binarycompatibilityvalidator/KLibDumpParser.kt
@@ -95,7 +95,6 @@
platformTargets = listOf(),
compilerVersion = "",
abiVersion = "",
- libraryVersion = "",
irProviderName = ""
)
)
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/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index 76cb02d..6f6e0b20 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -185,11 +185,7 @@
compile.pluginClasspath.from(kotlinPluginProvider.get())
- // todo(b/291587160): enable when Compose compiler 2.0.20 is merged
- // compile.enableFeatureFlag(ComposeFeatureFlag.StrongSkipping)
- // compile.enableFeatureFlag(ComposeFeatureFlag.OptimizeNonSkippingGroups)
- compile.addPluginOption(ComposeCompileOptions.StrongSkipping, "true")
- compile.addPluginOption(ComposeCompileOptions.NonSkippingGroupOptimization, "true")
+ compile.enableFeatureFlag(ComposeFeatureFlag.OptimizeNonSkippingGroups)
if (shouldPublish) {
compile.addPluginOption(ComposeCompileOptions.SourceOption, "true")
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
index 101493e..e178e58 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
@@ -150,6 +150,9 @@
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,
@@ -186,6 +189,7 @@
USE_JSPECIFY_ANNOTATIONS,
YARN_OFFLINE_MODE,
FORCE_KOTLIN_2_0_TARGET,
+ FORCE_BENCHMARK_AOT_COMPILATION,
) + AndroidConfigImpl.GRADLE_PROPERTIES
fun Project.shouldForceKotlin20Target() =
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 9b239ec4..f875c2f 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
@@ -822,11 +823,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 +835,7 @@
// Sign all the builds (including release) with debug key
buildType.signingConfig = debugSigningConfig
}
+ testBuildType = buildTypeForTests
project.configureTestConfigGeneration(this)
project.addAppApkToTestConfigGeneration(androidXExtension)
}
@@ -853,10 +855,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
}
@@ -1093,9 +1098,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/AndroidXMultiplatformExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
index 607b2c3..ad3c819 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
@@ -46,6 +46,7 @@
import org.gradle.kotlin.dsl.withType
import org.gradle.work.DisableCachingByDefault
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
+import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
@@ -56,7 +57,6 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithHostTests
-import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWasmTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.ir.DefaultIncrementalSyncTask
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/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/clang/KonanBuildService.kt b/buildSrc/private/src/main/kotlin/androidx/build/clang/KonanBuildService.kt
index 50f8f8ec..b0b2916 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/clang/KonanBuildService.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/clang/KonanBuildService.kt
@@ -35,7 +35,9 @@
import org.gradle.process.ExecSpec
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader
+import org.jetbrains.kotlin.konan.TempFiles
import org.jetbrains.kotlin.konan.target.Family
+import org.jetbrains.kotlin.konan.target.LinkerArguments
import org.jetbrains.kotlin.konan.target.LinkerOutputKind
import org.jetbrains.kotlin.konan.target.Platform
import org.jetbrains.kotlin.konan.target.PlatformManager
@@ -138,18 +140,22 @@
val objectFiles = parameters.objectFiles.regularFilePaths()
val linkedObjectFiles = parameters.linkedObjects.regularFilePaths()
val linkCommands =
- platform.linker.finalLinkCommands(
- objectFiles = objectFiles,
- executable = outputFile.canonicalPath,
- libraries = linkedObjectFiles,
- linkerArgs = linkerFlags,
- optimize = true,
- debug = false,
- kind = LinkerOutputKind.DYNAMIC_LIBRARY,
- outputDsymBundle = "unused",
- mimallocEnabled = false,
- sanitizer = null
- )
+ with(platform.linker) {
+ LinkerArguments(
+ TempFiles(),
+ objectFiles = objectFiles,
+ executable = outputFile.canonicalPath,
+ libraries = linkedObjectFiles,
+ linkerArgs = linkerFlags,
+ optimize = true,
+ debug = false,
+ kind = LinkerOutputKind.DYNAMIC_LIBRARY,
+ outputDsymBundle = "unused",
+ mimallocEnabled = false,
+ sanitizer = null
+ )
+ .finalLinkCommands()
+ }
linkCommands
.map { it.argsWithExecutable }
.forEach { args ->
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/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/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/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/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/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-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%
rename from activity/activity-compose/api/res-1.10.0-beta01.txt
rename 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-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/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/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-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/collection/collection/bcv/native/current.txt b/collection/collection/bcv/native/current.txt
index 215b18d..c3aa7c4 100644
--- a/collection/collection/bcv/native/current.txt
+++ b/collection/collection/bcv/native/current.txt
@@ -981,8 +981,6 @@
}
sealed class <#A: kotlin/Any?, #B: kotlin/Any?> androidx.collection/ScatterMap { // androidx.collection/ScatterMap|null[0]
- constructor <init>() // androidx.collection/ScatterMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/ScatterMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/ScatterMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/ScatterMap.size|{}size[0]
@@ -1024,8 +1022,6 @@
}
sealed class <#A: kotlin/Any?> androidx.collection/FloatObjectMap { // androidx.collection/FloatObjectMap|null[0]
- constructor <init>() // androidx.collection/FloatObjectMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/FloatObjectMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/FloatObjectMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/FloatObjectMap.size|{}size[0]
@@ -1067,8 +1063,6 @@
}
sealed class <#A: kotlin/Any?> androidx.collection/IntObjectMap { // androidx.collection/IntObjectMap|null[0]
- constructor <init>() // androidx.collection/IntObjectMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/IntObjectMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/IntObjectMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/IntObjectMap.size|{}size[0]
@@ -1110,8 +1104,6 @@
}
sealed class <#A: kotlin/Any?> androidx.collection/LongObjectMap { // androidx.collection/LongObjectMap|null[0]
- constructor <init>() // androidx.collection/LongObjectMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/LongObjectMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/LongObjectMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/LongObjectMap.size|{}size[0]
@@ -1153,8 +1145,6 @@
}
sealed class <#A: kotlin/Any?> androidx.collection/ObjectFloatMap { // androidx.collection/ObjectFloatMap|null[0]
- constructor <init>() // androidx.collection/ObjectFloatMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/ObjectFloatMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/ObjectFloatMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/ObjectFloatMap.size|{}size[0]
@@ -1197,8 +1187,6 @@
}
sealed class <#A: kotlin/Any?> androidx.collection/ObjectIntMap { // androidx.collection/ObjectIntMap|null[0]
- constructor <init>() // androidx.collection/ObjectIntMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/ObjectIntMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/ObjectIntMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/ObjectIntMap.size|{}size[0]
@@ -1241,8 +1229,6 @@
}
sealed class <#A: kotlin/Any?> androidx.collection/ObjectList { // androidx.collection/ObjectList|null[0]
- constructor <init>(kotlin/Int) // androidx.collection/ObjectList.<init>|<init>(kotlin.Int){}[0]
-
final val indices // androidx.collection/ObjectList.indices|{}indices[0]
final inline fun <get-indices>(): kotlin.ranges/IntRange // androidx.collection/ObjectList.indices.<get-indices>|<get-indices>(){}[0]
final val lastIndex // androidx.collection/ObjectList.lastIndex|{}lastIndex[0]
@@ -1301,8 +1287,6 @@
}
sealed class <#A: kotlin/Any?> androidx.collection/ObjectLongMap { // androidx.collection/ObjectLongMap|null[0]
- constructor <init>() // androidx.collection/ObjectLongMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/ObjectLongMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/ObjectLongMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/ObjectLongMap.size|{}size[0]
@@ -1345,8 +1329,6 @@
}
sealed class <#A: kotlin/Any?> androidx.collection/OrderedScatterSet { // androidx.collection/OrderedScatterSet|null[0]
- constructor <init>() // androidx.collection/OrderedScatterSet.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/OrderedScatterSet.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/OrderedScatterSet.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/OrderedScatterSet.size|{}size[0]
@@ -1396,8 +1378,6 @@
}
sealed class <#A: kotlin/Any?> androidx.collection/ScatterSet { // androidx.collection/ScatterSet|null[0]
- constructor <init>() // androidx.collection/ScatterSet.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/ScatterSet.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/ScatterSet.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/ScatterSet.size|{}size[0]
@@ -1432,8 +1412,6 @@
}
sealed class androidx.collection/DoubleList { // androidx.collection/DoubleList|null[0]
- constructor <init>(kotlin/Int) // androidx.collection/DoubleList.<init>|<init>(kotlin.Int){}[0]
-
final val indices // androidx.collection/DoubleList.indices|{}indices[0]
final inline fun <get-indices>(): kotlin.ranges/IntRange // androidx.collection/DoubleList.indices.<get-indices>|<get-indices>(){}[0]
final val lastIndex // androidx.collection/DoubleList.lastIndex|{}lastIndex[0]
@@ -1486,8 +1464,6 @@
}
sealed class androidx.collection/FloatFloatMap { // androidx.collection/FloatFloatMap|null[0]
- constructor <init>() // androidx.collection/FloatFloatMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/FloatFloatMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/FloatFloatMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/FloatFloatMap.size|{}size[0]
@@ -1530,8 +1506,6 @@
}
sealed class androidx.collection/FloatIntMap { // androidx.collection/FloatIntMap|null[0]
- constructor <init>() // androidx.collection/FloatIntMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/FloatIntMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/FloatIntMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/FloatIntMap.size|{}size[0]
@@ -1574,8 +1548,6 @@
}
sealed class androidx.collection/FloatList { // androidx.collection/FloatList|null[0]
- constructor <init>(kotlin/Int) // androidx.collection/FloatList.<init>|<init>(kotlin.Int){}[0]
-
final val indices // androidx.collection/FloatList.indices|{}indices[0]
final inline fun <get-indices>(): kotlin.ranges/IntRange // androidx.collection/FloatList.indices.<get-indices>|<get-indices>(){}[0]
final val lastIndex // androidx.collection/FloatList.lastIndex|{}lastIndex[0]
@@ -1628,8 +1600,6 @@
}
sealed class androidx.collection/FloatLongMap { // androidx.collection/FloatLongMap|null[0]
- constructor <init>() // androidx.collection/FloatLongMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/FloatLongMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/FloatLongMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/FloatLongMap.size|{}size[0]
@@ -1672,8 +1642,6 @@
}
sealed class androidx.collection/FloatSet { // androidx.collection/FloatSet|null[0]
- constructor <init>() // androidx.collection/FloatSet.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/FloatSet.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/FloatSet.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/FloatSet.size|{}size[0]
@@ -1707,8 +1675,6 @@
}
sealed class androidx.collection/IntFloatMap { // androidx.collection/IntFloatMap|null[0]
- constructor <init>() // androidx.collection/IntFloatMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/IntFloatMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/IntFloatMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/IntFloatMap.size|{}size[0]
@@ -1751,8 +1717,6 @@
}
sealed class androidx.collection/IntIntMap { // androidx.collection/IntIntMap|null[0]
- constructor <init>() // androidx.collection/IntIntMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/IntIntMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/IntIntMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/IntIntMap.size|{}size[0]
@@ -1795,8 +1759,6 @@
}
sealed class androidx.collection/IntList { // androidx.collection/IntList|null[0]
- constructor <init>(kotlin/Int) // androidx.collection/IntList.<init>|<init>(kotlin.Int){}[0]
-
final val indices // androidx.collection/IntList.indices|{}indices[0]
final inline fun <get-indices>(): kotlin.ranges/IntRange // androidx.collection/IntList.indices.<get-indices>|<get-indices>(){}[0]
final val lastIndex // androidx.collection/IntList.lastIndex|{}lastIndex[0]
@@ -1849,8 +1811,6 @@
}
sealed class androidx.collection/IntLongMap { // androidx.collection/IntLongMap|null[0]
- constructor <init>() // androidx.collection/IntLongMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/IntLongMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/IntLongMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/IntLongMap.size|{}size[0]
@@ -1893,8 +1853,6 @@
}
sealed class androidx.collection/IntSet { // androidx.collection/IntSet|null[0]
- constructor <init>() // androidx.collection/IntSet.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/IntSet.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/IntSet.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/IntSet.size|{}size[0]
@@ -1928,8 +1886,6 @@
}
sealed class androidx.collection/LongFloatMap { // androidx.collection/LongFloatMap|null[0]
- constructor <init>() // androidx.collection/LongFloatMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/LongFloatMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/LongFloatMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/LongFloatMap.size|{}size[0]
@@ -1972,8 +1928,6 @@
}
sealed class androidx.collection/LongIntMap { // androidx.collection/LongIntMap|null[0]
- constructor <init>() // androidx.collection/LongIntMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/LongIntMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/LongIntMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/LongIntMap.size|{}size[0]
@@ -2016,8 +1970,6 @@
}
sealed class androidx.collection/LongList { // androidx.collection/LongList|null[0]
- constructor <init>(kotlin/Int) // androidx.collection/LongList.<init>|<init>(kotlin.Int){}[0]
-
final val indices // androidx.collection/LongList.indices|{}indices[0]
final inline fun <get-indices>(): kotlin.ranges/IntRange // androidx.collection/LongList.indices.<get-indices>|<get-indices>(){}[0]
final val lastIndex // androidx.collection/LongList.lastIndex|{}lastIndex[0]
@@ -2070,8 +2022,6 @@
}
sealed class androidx.collection/LongLongMap { // androidx.collection/LongLongMap|null[0]
- constructor <init>() // androidx.collection/LongLongMap.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/LongLongMap.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/LongLongMap.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/LongLongMap.size|{}size[0]
@@ -2114,8 +2064,6 @@
}
sealed class androidx.collection/LongSet { // androidx.collection/LongSet|null[0]
- constructor <init>() // androidx.collection/LongSet.<init>|<init>(){}[0]
-
final val capacity // androidx.collection/LongSet.capacity|{}capacity[0]
final fun <get-capacity>(): kotlin/Int // androidx.collection/LongSet.capacity.<get-capacity>|<get-capacity>(){}[0]
final val size // androidx.collection/LongSet.size|{}size[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/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/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/Transition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
index 1fc3a21..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
@@ -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/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..3987062
--- /dev/null
+++ b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimateBoundsTest.kt
@@ -0,0 +1,561 @@
+/*
+ * 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.lerp
+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.Ignore
+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 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)
+
+ 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()
+ rule.mainClock.advanceTimeByFrame()
+ rule.mainClock.advanceTimeByFrame()
+
+ // 1-frame latency. TODO: Can we fix this?
+ rule.mainClock.advanceTimeByFrame()
+
+ // Advance to approx. the middle of the animation
+ rule.mainClock.advanceTimeBy(durationMillis / 2L)
+
+ 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)
+ }
+
+ @Ignore("b/362340158 - Around 10% flaky on local tests")
+ @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) }
+
+ // 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
+
+ // Not sure why, but we need to run this scroll within a runOnIdle to complete the test
+ // consistently across devices.
+ rule.runOnIdle {
+ runBlocking {
+ // Scroll back into starting position
+ scrollState.scrollBy(-itemSizePx)
+ }
+ }
+
+ rule.mainClock.advanceTimeByFrame()
+ 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/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index bd0cb05..8b5adeb 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -41,7 +41,7 @@
}
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.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);
+ 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 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 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);
}
@@ -209,7 +209,7 @@
}
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.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);
+ 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 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 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);
}
@@ -229,10 +229,10 @@
}
@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;
}
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index bd0cb05..8b5adeb 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -41,7 +41,7 @@
}
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.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);
+ 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 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 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);
}
@@ -209,7 +209,7 @@
}
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.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);
+ 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 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 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);
}
@@ -229,10 +229,10 @@
}
@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;
}
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/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/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 5bb4f4c..c25d25c 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,10 @@
* @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 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
@@ -102,6 +100,11 @@
* @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 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
@@ -112,6 +115,9 @@
detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
modifier: Modifier = Modifier,
extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
+ paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
+ null,
+ paneExpansionState: PaneExpansionState = rememberPaneExpansionState(scaffoldState.targetState),
) {
ThreePaneScaffold(
modifier = modifier.fillMaxSize(),
@@ -120,6 +126,8 @@
paneOrder = ListDetailPaneScaffoldDefaults.PaneOrder,
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/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/SupportingPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
index 7b8abec..de8d35d 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,10 @@
* @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 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
@@ -95,6 +93,11 @@
* @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 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
@@ -105,6 +108,9 @@
supportingPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
modifier: Modifier = Modifier,
extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
+ paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? =
+ null,
+ paneExpansionState: PaneExpansionState = rememberPaneExpansionState(scaffoldState.targetState),
) {
ThreePaneScaffold(
modifier = modifier.fillMaxSize(),
@@ -113,6 +119,8 @@
paneOrder = SupportingPaneScaffoldDefaults.PaneOrder,
secondaryPane = supportingPane,
tertiaryPane = extraPane,
+ paneExpansionDragHandle = paneExpansionDragHandle,
+ paneExpansionState = paneExpansionState,
primaryPane = mainPane
)
}
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-navigation/api/current.txt b/compose/material3/adaptive/adaptive-navigation/api/current.txt
index a923bcc..ac6ed05 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/current.txt
@@ -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 a923bcc..ac6ed05 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
@@ -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/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/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/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/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/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/core/core-telecom/src/main/res/values-af/strings.xml b/core/core-telecom/src/main/res/values-af/strings.xml
new file mode 100644
index 0000000..e5c55e0
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-af/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">"Oorstuk"</string>
+ <string name="callendpoint_name_wiredheadset" msgid="6723516311603411573">"Kabelkopstuk"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Luidspreker"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-am/strings.xml b/core/core-telecom/src/main/res/values-am/strings.xml
new file mode 100644
index 0000000..66301fb
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-am/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-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/core/core-telecom/src/main/res/values-az/strings.xml b/core/core-telecom/src/main/res/values-az/strings.xml
new file mode 100644
index 0000000..e62a4d0
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-az/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">"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/core/core-telecom/src/main/res/values-b+sr+Latn/strings.xml b/core/core-telecom/src/main/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..6afc996
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-b+sr+Latn/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">"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-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/core/core-telecom/src/main/res/values-bs/strings.xml b/core/core-telecom/src/main/res/values-bs/strings.xml
new file mode 100644
index 0000000..6afc996
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-bs/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">"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-ca/strings.xml b/core/core-telecom/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000..4b8b784
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ca/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">"Auriculars amb cable"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Altaveu"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-cs/strings.xml b/core/core-telecom/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000..9bafe17
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-cs/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">"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/core/core-telecom/src/main/res/values-da/strings.xml b/core/core-telecom/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000..257ec4e
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-da/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">"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/core/core-telecom/src/main/res/values-de/strings.xml b/core/core-telecom/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..0ce2c07
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-de/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">"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/core/core-telecom/src/main/res/values-en-rAU/strings.xml b/core/core-telecom/src/main/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..ae0ae39
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-en-rAU/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/core/core-telecom/src/main/res/values-en-rGB/strings.xml b/core/core-telecom/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..ae0ae39
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-en-rGB/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/core/core-telecom/src/main/res/values-en-rIN/strings.xml b/core/core-telecom/src/main/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..ae0ae39
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-en-rIN/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/core/core-telecom/src/main/res/values-es-rUS/strings.xml b/core/core-telecom/src/main/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..1b87fa9
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-es-rUS/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">"Auriculares con cable"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Bocina"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-es/strings.xml b/core/core-telecom/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..5af28da
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-es/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">"Auriculares con cable"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Altavoz"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-et/strings.xml b/core/core-telecom/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000..3d2cc0c
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-et/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">"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/core/core-telecom/src/main/res/values-eu/strings.xml b/core/core-telecom/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000..16cc5e9
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-eu/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">"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/core/core-telecom/src/main/res/values-fa/strings.xml b/core/core-telecom/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000..e159d7c
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-fa/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-fr-rCA/strings.xml b/core/core-telecom/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..33d0ca4
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-fr-rCA/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">"É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/core/core-telecom/src/main/res/values-fr/strings.xml b/core/core-telecom/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..efba6ec
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-fr/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">"É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/core/core-telecom/src/main/res/values-gl/strings.xml b/core/core-telecom/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000..73f3cf5
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-gl/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">"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-hr/strings.xml b/core/core-telecom/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000..a812b93
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-hr/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">"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/core/core-telecom/src/main/res/values-hu/strings.xml b/core/core-telecom/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000..1175ec2
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-hu/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">"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/core/core-telecom/src/main/res/values-hy/strings.xml b/core/core-telecom/src/main/res/values-hy/strings.xml
new file mode 100644
index 0000000..0f1a0d2
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-hy/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-in/strings.xml b/core/core-telecom/src/main/res/values-in/strings.xml
new file mode 100644
index 0000000..8e053cc
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-in/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">"Headset berkabel"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Speaker"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-is/strings.xml b/core/core-telecom/src/main/res/values-is/strings.xml
new file mode 100644
index 0000000..c0befd0
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-is/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">"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/core/core-telecom/src/main/res/values-iw/strings.xml b/core/core-telecom/src/main/res/values-iw/strings.xml
new file mode 100644
index 0000000..9ec7ee9
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-iw/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/core/core-telecom/src/main/res/values-ko/strings.xml b/core/core-telecom/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000..6dd6a7d
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ko/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-ky/strings.xml b/core/core-telecom/src/main/res/values-ky/strings.xml
new file mode 100644
index 0000000..4de5b55
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ky/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-lt/strings.xml b/core/core-telecom/src/main/res/values-lt/strings.xml
new file mode 100644
index 0000000..ab5b490
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-lt/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">"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/core/core-telecom/src/main/res/values-lv/strings.xml b/core/core-telecom/src/main/res/values-lv/strings.xml
new file mode 100644
index 0000000..dbbe825
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-lv/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">"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/core/core-telecom/src/main/res/values-mk/strings.xml b/core/core-telecom/src/main/res/values-mk/strings.xml
new file mode 100644
index 0000000..73a43de
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-mk/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/core/core-telecom/src/main/res/values-ms/strings.xml b/core/core-telecom/src/main/res/values-ms/strings.xml
new file mode 100644
index 0000000..1ffead0
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ms/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">"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/core/core-telecom/src/main/res/values-nb/strings.xml b/core/core-telecom/src/main/res/values-nb/strings.xml
new file mode 100644
index 0000000..387722e
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-nb/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">"Ø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-nl/strings.xml b/core/core-telecom/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000..c496439
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-nl/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">"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/core/core-telecom/src/main/res/values-pl/strings.xml b/core/core-telecom/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000..768b794
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-pl/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">"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/core/core-telecom/src/main/res/values-pt-rBR/strings.xml b/core/core-telecom/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..de52295
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-pt-rBR/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">"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/strings.xml b/core/core-telecom/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000..de52295
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-pt/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">"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-ro/strings.xml b/core/core-telecom/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000..a1b63da
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ro/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">"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/core/core-telecom/src/main/res/values-sk/strings.xml b/core/core-telecom/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000..b6075f4
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-sk/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">"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/core/core-telecom/src/main/res/values-sl/strings.xml b/core/core-telecom/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000..5ca1886
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-sl/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">"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/core/core-telecom/src/main/res/values-sq/strings.xml b/core/core-telecom/src/main/res/values-sq/strings.xml
new file mode 100644
index 0000000..c932e8d
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-sq/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">"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/core/core-telecom/src/main/res/values-sr/strings.xml b/core/core-telecom/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000..199a682
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-sr/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-sv/strings.xml b/core/core-telecom/src/main/res/values-sv/strings.xml
new file mode 100644
index 0000000..aec1523
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-sv/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">"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/core/core-telecom/src/main/res/values-sw/strings.xml b/core/core-telecom/src/main/res/values-sw/strings.xml
new file mode 100644
index 0000000..af27136d
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-sw/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">"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/core/core-telecom/src/main/res/values-tl/strings.xml b/core/core-telecom/src/main/res/values-tl/strings.xml
new file mode 100644
index 0000000..50ac56c
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-tl/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 na headset"</string>
+ <string name="callendpoint_name_speaker" msgid="623806810712383295">"Speaker"</string>
+</resources>
diff --git a/core/core-telecom/src/main/res/values-tr/strings.xml b/core/core-telecom/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..600c6d2
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-tr/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">"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/core/core-telecom/src/main/res/values-uk/strings.xml b/core/core-telecom/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000..d8580f9
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-uk/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-ur/strings.xml b/core/core-telecom/src/main/res/values-ur/strings.xml
new file mode 100644
index 0000000..d577f0b
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-ur/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-uz/strings.xml b/core/core-telecom/src/main/res/values-uz/strings.xml
new file mode 100644
index 0000000..679f5dd
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-uz/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">"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/core/core-telecom/src/main/res/values-vi/strings.xml b/core/core-telecom/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000..a4c82f9
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-vi/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">"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/core/core-telecom/src/main/res/values-zh-rCN/strings.xml b/core/core-telecom/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..0d4efb7
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-zh-rCN/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-zh-rHK/strings.xml b/core/core-telecom/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..4e99591
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-zh-rHK/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-zh-rTW/strings.xml b/core/core-telecom/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..4c586c7
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-zh-rTW/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-zu/strings.xml b/core/core-telecom/src/main/res/values-zu/strings.xml
new file mode 100644
index 0000000..7aab3c5
--- /dev/null
+++ b/core/core-telecom/src/main/res/values-zu/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">"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/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/integration-tests/publishing/src/test/kotlin/androidx/build/ConstraintTest.kt b/core/core/integration-tests/publishing/src/test/kotlin/androidx/build/ConstraintTest.kt
index 545a3d6..c96cd6d 100644
--- a/core/core/integration-tests/publishing/src/test/kotlin/androidx/build/ConstraintTest.kt
+++ b/core/core/integration-tests/publishing/src/test/kotlin/androidx/build/ConstraintTest.kt
@@ -63,15 +63,32 @@
}
}
],
- "dependencyConstraints": [
+ variants: [
{
- "group": "androidx.preference",
- "module": "preference-ktx",
- "version": {
- "requires": "1.3.0-alpha01"
- }
- }
- ],
+ "name": "releaseVariantReleaseApiPublication",
+ "dependencyConstraints": [
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-stdlib",
+ "version": {
+ "requires": "1.8.22"
+ }
+ }
+ ],
+ },
+ {
+ "name": "releaseVariantReleaseRuntimePublication",
+ "dependencyConstraints": [
+ {
+ "group": "androidx.preference",
+ "module": "preference-ktx",
+ "version": {
+ "requires": "1.3.0-alpha01"
+ }
+ }
+ ],
+ }
+ ]
"files": [
{
"name": "preference-1.3.0-alpha01.aar",
@@ -85,7 +102,7 @@
}
private fun getConstraintVersion(metadata: String, groupId: String, artifact: String): String? =
- getDependencyConstraints(metadata)?.let {
+ getDependencyConstraints(metadata).let {
Regex(
"\"group\": \"$groupId\",\\s+\"module\": " +
"\"$artifact\",\\s+\"version\": \\{\\s+\"requires\": \"(.+?)\""
@@ -97,8 +114,8 @@
}
private fun getDependencyConstraints(moduleJson: String) =
- moduleJson.let {
- Regex("(?s)\"dependencyConstraints\": \\[(.+?)]").find(it)?.groups?.get(1)?.value
+ Regex("(?s)\"dependencyConstraints\": \\[(.+?)]").findAll(moduleJson).joinToString("\n") {
+ it.groups.get(1)?.value.orEmpty()
}
// Yes, I know https://stackoverflow.com/a/1732454/258688, but it's just a test...
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 b73acb1..683e0fbb 100644
--- a/core/core/src/main/java/androidx/core/os/Profiling.kt
+++ b/core/core/src/main/java/androidx/core/os/Profiling.kt
@@ -64,7 +64,11 @@
private class RingBuffer : BufferFillPolicy(VALUE_BUFFER_FILL_POLICY_RING_BUFFER)
}
-/** Obtain a flow to be called with all profiling results for this UID. */
+/**
+ * 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) }
@@ -75,7 +79,11 @@
awaitClose { service.unregisterForAllProfilingResults(listener) }
}
-/** Register a listener to be called with all profiling results for this UID. */
+/**
+ * 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,
@@ -98,6 +106,19 @@
*
* If the executor and/or listener are null, and if no global listener and executor combinations are
* 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 = 35)
public fun requestProfiling(
@@ -164,7 +185,11 @@
protected abstract fun getParams(): Bundle
}
-/** Request builder to create a request for a java heap dump from [ProfilingManager]. */
+/**
+ * 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()
@@ -191,7 +216,11 @@
}
}
-/** Request builder to create a request for a heap profile from [ProfilingManager]. */
+/**
+ * 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()
@@ -236,7 +265,11 @@
}
}
-/** Request builder to create a request for stack sampling from [ProfilingManager]. */
+/**
+ * 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()
@@ -275,7 +308,11 @@
}
}
-/** Request builder to create a request for a system trace from [ProfilingManager]. */
+/**
+ * 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()
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/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/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 9c8f089..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
diff --git a/development/update_kotlin.sh b/development/update_kotlin.sh
index b6f492a..eff00fd 100755
--- a/development/update_kotlin.sh
+++ b/development/update_kotlin.sh
@@ -2,14 +2,12 @@
set -e
KOTLIN_VERSION="$1"
+KSP_VERSION="$2"
-ALLOW_JETBRAINS_DEV=""
-for arg in "$@"
-do
- if [ "$arg" == "--allow-jetbrains-dev" ]; then
- ALLOW_JETBRAINS_DEV="--allow-jetbrains-dev"
- fi
-done
+if [[ $# -eq 0 ]] ; then
+ echo "Usage ./development/update_kotlin.sh <kotlin_version> [<ksp_version>]"
+ exit 1
+fi
# Download maven artifacts
ARTIFACTS_TO_DOWNLOAD="org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION,"
@@ -38,7 +36,39 @@
ARTIFACTS_TO_DOWNLOAD+="org.jetbrains.kotlin:kotlin-native-prebuilt:$KOTLIN_VERSION:[email protected],"
ARTIFACTS_TO_DOWNLOAD+="org.jetbrains.kotlin:kotlin-native-prebuilt:$KOTLIN_VERSION:[email protected],"
-./development/importMaven/importMaven.sh "$ALLOW_JETBRAINS_DEV" "$ARTIFACTS_TO_DOWNLOAD"
+if [ "$KSP_VERSION" ]; then
+ ARTIFACTS_TO_DOWNLOAD+="com.google.devtools.ksp:symbol-processing:$KSP_VERSION,"
+ ARTIFACTS_TO_DOWNLOAD+="com.google.devtools.ksp:symbol-processing-api:$KSP_VERSION,"
+ ARTIFACTS_TO_DOWNLOAD+="com.google.devtools.ksp:symbol-processing-cmdline:$KSP_VERSION,"
+ ARTIFACTS_TO_DOWNLOAD+="com.google.devtools.ksp:symbol-processing-gradle-plugin:$KSP_VERSION,"
+ ARTIFACTS_TO_DOWNLOAD+="com.google.devtools.ksp:symbol-processing-aa-embeddable:$KSP_VERSION,"
+fi
- ./development/importMaven/importMaven.sh import-konan-binaries --konan-compiler-version "$KOTLIN_VERSION"
+./development/importMaven/importMaven.sh "$ARTIFACTS_TO_DOWNLOAD"
+# symlink native compiler prebuilt archives from prebuilts/androidx/external to prebuilts/androidx/konan
+# to make KonanPrebuiltsSetup.kt work.
+rm -fr "../../prebuilts/androidx/konan/nativeCompilerPrebuilts/releases"
+
+REAL_NATIVE_PREBUILT_DIR="../../../../../external/org/jetbrains/kotlin/kotlin-native-prebuilt/$KOTLIN_VERSION/"
+
+LINUX_DIR="../../prebuilts/androidx/konan/nativeCompilerPrebuilts/releases/$KOTLIN_VERSION/linux-x86_64"
+mkdir -p "$LINUX_DIR"
+ln -s -f "$REAL_NATIVE_PREBUILT_DIR/kotlin-native-prebuilt-$KOTLIN_VERSION-linux-x86_64.tar.gz" \
+ "$LINUX_DIR/kotlin-native-prebuilt-linux-x86_64-$KOTLIN_VERSION.tar.gz"
+ln -s -f "$REAL_NATIVE_PREBUILT_DIR/kotlin-native-prebuilt-$KOTLIN_VERSION-linux-x86_64.tar.gz.asc" \
+ "$LINUX_DIR/kotlin-native-prebuilt-linux-x86_64-$KOTLIN_VERSION.tar.gz.asc"
+
+MAC_ARM_DIR="../../prebuilts/androidx/konan/nativeCompilerPrebuilts/releases/$KOTLIN_VERSION/macos-aarch64"
+mkdir -p "$MAC_ARM_DIR"
+ln -s -f "$REAL_NATIVE_PREBUILT_DIR/kotlin-native-prebuilt-$KOTLIN_VERSION-macos-aarch64.tar.gz" \
+ "$MAC_ARM_DIR/kotlin-native-prebuilt-macos-aarch64-$KOTLIN_VERSION.tar.gz"
+ln -s -f "$REAL_NATIVE_PREBUILT_DIR/kotlin-native-prebuilt-$KOTLIN_VERSION-macos-aarch64.tar.gz.asc" \
+ "$MAC_ARM_DIR/kotlin-native-prebuilt-macos-aarch64-$KOTLIN_VERSION.tar.gz.asc"
+
+MAC_X86_DIR="../../prebuilts/androidx/konan/nativeCompilerPrebuilts/releases/$KOTLIN_VERSION/macos-x86_64"
+mkdir -p "$MAC_X86_DIR"
+ln -s -f "$REAL_NATIVE_PREBUILT_DIR/kotlin-native-prebuilt-$KOTLIN_VERSION-macos-x86_64.tar.gz" \
+ "$MAC_X86_DIR/kotlin-native-prebuilt-macos-x86_64-$KOTLIN_VERSION.tar.gz"
+ln -s -f "$REAL_NATIVE_PREBUILT_DIR/kotlin-native-prebuilt-$KOTLIN_VERSION-macos-x86_64.tar.gz.asc" \
+ "$MAC_X86_DIR/kotlin-native-prebuilt-macos-x86_64-$KOTLIN_VERSION.tar.gz.asc"
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 5704cee..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"))
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/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5165817..692ca84 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -44,7 +44,7 @@
kotlin17 = "1.7.10"
kotlin18 = "1.8.22"
kotlin19 = "1.9.24"
-kotlin = "2.0.10"
+kotlin = "2.0.20"
kotlinBenchmark = "0.4.11"
kotlinGradlePluginAnnotations = "1.9.24"
kotlinGradlePluginApi = "1.9.24"
@@ -53,7 +53,7 @@
kotlinNativeUtils = "1.9.24"
kotlinSerialization = "1.6.3"
kotlinToolingCore = "1.9.24"
-ksp = "2.0.10-1.0.24"
+ksp = "2.0.20-1.0.24"
ktfmt = "0.50"
leakcanary = "2.13"
media3 = "1.1.0"
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 368f7b1..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
@@ -229,6 +229,29 @@
}
}
+ @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 051588a..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
@@ -74,7 +74,7 @@
}
}
}
- if (lastCubic != null && firstCubic != null)
+ if (lastCubic != null && firstCubic != null) {
add(
Cubic(
lastCubic.anchor0X,
@@ -87,6 +87,21 @@
firstCubic.anchor0Y
)
)
+ } else {
+ // Empty / 0-sized polygon.
+ add(
+ Cubic(
+ centerX,
+ centerY,
+ centerX,
+ centerY,
+ centerX,
+ centerY,
+ centerX,
+ centerY,
+ )
+ )
+ }
}
init {
@@ -100,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
@@ -483,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/libraryversions.toml b/libraryversions.toml
index 85d5aab..b33669b 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -1,5 +1,5 @@
[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"
@@ -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"
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 dee1a74..4799f00 100644
--- a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
@@ -86,6 +86,7 @@
// 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/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/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/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/paging/paging-common/bcv/native/current.txt b/paging/paging-common/bcv/native/current.txt
index 64890c9..61b670d 100644
--- a/paging/paging-common/bcv/native/current.txt
+++ b/paging/paging-common/bcv/native/current.txt
@@ -70,8 +70,6 @@
final fun unregisterInvalidatedCallback(kotlin/Function0<kotlin/Unit>) // androidx.paging/PagingSource.unregisterInvalidatedCallback|unregisterInvalidatedCallback(kotlin.Function0<kotlin.Unit>){}[0]
sealed class <#A1: kotlin/Any, #B1: kotlin/Any> LoadResult { // androidx.paging/PagingSource.LoadResult|null[0]
- constructor <init>() // androidx.paging/PagingSource.LoadResult.<init>|<init>(){}[0]
-
final class <#A2: kotlin/Any, #B2: kotlin/Any> Error : androidx.paging/PagingSource.LoadResult<#A2, #B2> { // androidx.paging/PagingSource.LoadResult.Error|null[0]
constructor <init>(kotlin/Throwable) // androidx.paging/PagingSource.LoadResult.Error.<init>|<init>(kotlin.Throwable){}[0]
@@ -125,8 +123,6 @@
}
sealed class <#A1: kotlin/Any> LoadParams { // androidx.paging/PagingSource.LoadParams|null[0]
- constructor <init>(kotlin/Int, kotlin/Boolean) // androidx.paging/PagingSource.LoadParams.<init>|<init>(kotlin.Int;kotlin.Boolean){}[0]
-
abstract val key // androidx.paging/PagingSource.LoadParams.key|{}key[0]
abstract fun <get-key>(): #A1? // androidx.paging/PagingSource.LoadParams.key.<get-key>|<get-key>(){}[0]
final val loadSize // androidx.paging/PagingSource.LoadParams.loadSize|{}loadSize[0]
@@ -172,8 +168,6 @@
}
sealed class MediatorResult { // androidx.paging/RemoteMediator.MediatorResult|null[0]
- constructor <init>() // androidx.paging/RemoteMediator.MediatorResult.<init>|<init>(){}[0]
-
final class Error : androidx.paging/RemoteMediator.MediatorResult { // androidx.paging/RemoteMediator.MediatorResult.Error|null[0]
constructor <init>(kotlin/Throwable) // androidx.paging/RemoteMediator.MediatorResult.Error.<init>|<init>(kotlin.Throwable){}[0]
@@ -342,8 +336,6 @@
}
sealed class <#A: kotlin/Any> androidx.paging/PagingDataEvent { // androidx.paging/PagingDataEvent|null[0]
- constructor <init>() // androidx.paging/PagingDataEvent.<init>|<init>(){}[0]
-
final class <#A1: kotlin/Any> Append : androidx.paging/PagingDataEvent<#A1> { // androidx.paging/PagingDataEvent.Append|null[0]
constructor <init>(kotlin/Int, kotlin.collections/List<#A1>, kotlin/Int, kotlin/Int) // androidx.paging/PagingDataEvent.Append.<init>|<init>(kotlin.Int;kotlin.collections.List<1:0>;kotlin.Int;kotlin.Int){}[0]
@@ -423,8 +415,6 @@
}
sealed class androidx.paging/LoadState { // androidx.paging/LoadState|null[0]
- constructor <init>(kotlin/Boolean) // androidx.paging/LoadState.<init>|<init>(kotlin.Boolean){}[0]
-
final val endOfPaginationReached // androidx.paging/LoadState.endOfPaginationReached|{}endOfPaginationReached[0]
final fun <get-endOfPaginationReached>(): kotlin/Boolean // androidx.paging/LoadState.endOfPaginationReached.<get-endOfPaginationReached>|<get-endOfPaginationReached>(){}[0]
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/pdf-viewer/src/main/res/values-af/strings.xml b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
index 269c461..3efa5d2 100644
--- a/pdf/pdf-viewer/src/main/res/values-af/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
@@ -53,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 517f867..a2679692 100644
--- a/pdf/pdf-viewer/src/main/res/values-am/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-am/strings.xml
@@ -53,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 3b33c01..eae5948 100644
--- a/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
@@ -53,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 6aff8a9eb..f9c9ef7 100644
--- a/pdf/pdf-viewer/src/main/res/values-as/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-as/strings.xml
@@ -53,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 622b95f..6ef1751 100644
--- a/pdf/pdf-viewer/src/main/res/values-az/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-az/strings.xml
@@ -53,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 0495044..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
@@ -53,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 6d25879..c951b2c 100644
--- a/pdf/pdf-viewer/src/main/res/values-be/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-be/strings.xml
@@ -53,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 37f6840..004be66 100644
--- a/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
@@ -53,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 a65a18c..937be09 100644
--- a/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
@@ -53,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 642b643..cd49b7a 100644
--- a/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
@@ -53,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 9338452..4f3deac 100644
--- a/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
@@ -53,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 07d4e4e..cdc4b65 100644
--- a/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
@@ -53,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 9ad79da..5bcd02c 100644
--- a/pdf/pdf-viewer/src/main/res/values-da/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-da/strings.xml
@@ -53,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 45bac2a..72b1096 100644
--- a/pdf/pdf-viewer/src/main/res/values-de/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-de/strings.xml
@@ -53,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 e87a5b8..ada33d8 100644
--- a/pdf/pdf-viewer/src/main/res/values-el/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-el/strings.xml
@@ -53,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 62dcae9..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
@@ -53,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 5a09605..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
@@ -53,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 62dcae9..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
@@ -53,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 62dcae9..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
@@ -53,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 d2bf4b8..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
@@ -53,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 4ad89b2..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
@@ -42,12 +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>
- <!-- no translation found for previous_button_description (1169511027880317546) -->
- <skip />
- <!-- no translation found for next_button_description (4702699322249103693) -->
- <skip />
- <!-- no translation found for close_button_description (7379823906921067675) -->
- <skip />
+ <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 d08f531..b1aca5f 100644
--- a/pdf/pdf-viewer/src/main/res/values-es/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-es/strings.xml
@@ -42,12 +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>
- <!-- no translation found for previous_button_description (1169511027880317546) -->
- <skip />
- <!-- no translation found for next_button_description (4702699322249103693) -->
- <skip />
- <!-- no translation found for close_button_description (7379823906921067675) -->
- <skip />
+ <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 902cdd0..d640ef5 100644
--- a/pdf/pdf-viewer/src/main/res/values-et/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-et/strings.xml
@@ -53,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 239b72c..20770aa 100644
--- a/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
@@ -53,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 42aa1cd..0669b17 100644
--- a/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
@@ -53,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 cdce79c..3518c19 100644
--- a/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
@@ -53,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 453aacf..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
@@ -53,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 cffc34d..dd5fcad 100644
--- a/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
@@ -53,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 67960e4..ebe0816 100644
--- a/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
@@ -53,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 f0fe5ae..ae95929 100644
--- a/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
@@ -53,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 d1fe782..49f8c1d 100644
--- a/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
@@ -53,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 4605373..7248c6ee 100644
--- a/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
@@ -53,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 a733547..f63a921 100644
--- a/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
@@ -53,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 901715c..71d24c2 100644
--- a/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
@@ -53,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 fa9c752..3c9e624 100644
--- a/pdf/pdf-viewer/src/main/res/values-in/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-in/strings.xml
@@ -53,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 9716ab1..9378e05 100644
--- a/pdf/pdf-viewer/src/main/res/values-is/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-is/strings.xml
@@ -53,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 564eecd..89a8d17 100644
--- a/pdf/pdf-viewer/src/main/res/values-it/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-it/strings.xml
@@ -53,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 6ce9431..3dfd510 100644
--- a/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
@@ -53,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 f03d62a..a48cc46 100644
--- a/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
@@ -53,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 864fd74..3fd5c9b 100644
--- a/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
@@ -53,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 b94ea34..5846202 100644
--- a/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
@@ -53,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 03a0ebd..931c43f 100644
--- a/pdf/pdf-viewer/src/main/res/values-km/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-km/strings.xml
@@ -53,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 4c114f7..fb8b009 100644
--- a/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
@@ -53,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 54655ec..9530691 100644
--- a/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
@@ -53,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 90e6450..92e5119 100644
--- a/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
@@ -53,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 895280e..baa1c28 100644
--- a/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
@@ -53,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 8b14e98..588ff87 100644
--- a/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
@@ -53,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 22e20c9..738615d 100644
--- a/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
@@ -53,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 cce9ac3..e6b85c2 100644
--- a/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
@@ -53,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 b07acaa..760a696 100644
--- a/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
@@ -53,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 756390b..9c8f052 100644
--- a/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
@@ -53,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 a6d1c6e..40ee1a8 100644
--- a/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
@@ -53,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 acf0ee8..f905244 100644
--- a/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
@@ -53,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 bd311a7..c288df3 100644
--- a/pdf/pdf-viewer/src/main/res/values-my/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-my/strings.xml
@@ -53,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 23107ac..06e5547 100644
--- a/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
@@ -53,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 da53207..d847ed0c 100644
--- a/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
@@ -53,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-nl/strings.xml b/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
index 9ad4b40..5ceea0c 100644
--- a/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
@@ -53,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 cf3e282..66b7f68 100644
--- a/pdf/pdf-viewer/src/main/res/values-or/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-or/strings.xml
@@ -53,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 86a73eb..8d4c105 100644
--- a/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
@@ -53,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 ce974f6..6583cb5 100644
--- a/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
@@ -53,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 f3fdf6c..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
@@ -53,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 ff10bc1..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
@@ -53,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 f3fdf6c..e8beb95 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
@@ -53,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 669086d..ef6b73d 100644
--- a/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
@@ -53,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 d7163f2..665e7cb 100644
--- a/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
@@ -53,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 bba16c5..dc5d075 100644
--- a/pdf/pdf-viewer/src/main/res/values-si/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-si/strings.xml
@@ -53,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 f8f3997..d44c7b6 100644
--- a/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
@@ -53,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 ad7b4cd..afef99a 100644
--- a/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
@@ -53,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 0e775ba..37347d3 100644
--- a/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
@@ -53,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 d70749c..bcab510 100644
--- a/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
@@ -53,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 eae0c6a..3fa349f 100644
--- a/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
@@ -53,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 e976caf..abf9c4b5 100644
--- a/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
@@ -53,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 8a51fa4..2d1d291 100644
--- a/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
@@ -53,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 e5ff775..ffcee7a 100644
--- a/pdf/pdf-viewer/src/main/res/values-te/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-te/strings.xml
@@ -53,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 971e3e2..be31750 100644
--- a/pdf/pdf-viewer/src/main/res/values-th/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-th/strings.xml
@@ -53,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 be6d695..5b20647 100644
--- a/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
@@ -53,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 09803ed..8ad6bf2 100644
--- a/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
@@ -53,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 fc81a4b..c381f20 100644
--- a/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
@@ -53,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 e733c19..6027e36 100644
--- a/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
@@ -53,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 54c3148..41b89f8 100644
--- a/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
@@ -53,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-vi/strings.xml b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
index c2015bc..5f50634 100644
--- a/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
@@ -53,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-zh-rCN/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
index 4abd99c..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
@@ -53,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 3510530..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
@@ -53,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 10fd054..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
@@ -53,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 4065bd2..ba456e5 100644
--- a/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
@@ -53,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/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/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/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/KotlinMetadataTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/KotlinMetadataTest.kt
index 7c21438..a1cddb7 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/KotlinMetadataTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/KotlinMetadataTest.kt
@@ -24,6 +24,7 @@
import androidx.room.compiler.processing.util.getParameter
import androidx.room.compiler.processing.util.runKaptTest
import androidx.room.compiler.processing.util.runProcessorTest
+import org.junit.Ignore
import org.junit.Test
class KotlinMetadataTest {
@@ -48,6 +49,7 @@
}
}
+ @Ignore("b/360398921")
@Test
fun inlineReifiedFunctionAndKAPT4() {
val source =
diff --git a/room/room-migration/bcv/native/current.txt b/room/room-migration/bcv/native/current.txt
index 0b1314c..9b3bd15 100644
--- a/room/room-migration/bcv/native/current.txt
+++ b/room/room-migration/bcv/native/current.txt
@@ -337,9 +337,6 @@
}
sealed class androidx.room.migration.bundle/BaseEntityBundle { // androidx.room.migration.bundle/BaseEntityBundle|null[0]
- constructor <init>() // androidx.room.migration.bundle/BaseEntityBundle.<init>|<init>(){}[0]
- constructor <init>(kotlin/Int, kotlinx.serialization.internal/SerializationConstructorMarker?) // androidx.room.migration.bundle/BaseEntityBundle.<init>|<init>(kotlin.Int;kotlinx.serialization.internal.SerializationConstructorMarker?){}[0]
-
abstract val createSql // androidx.room.migration.bundle/BaseEntityBundle.createSql|{}createSql[0]
abstract fun <get-createSql>(): kotlin/String // androidx.room.migration.bundle/BaseEntityBundle.createSql.<get-createSql>|<get-createSql>(){}[0]
abstract val fields // androidx.room.migration.bundle/BaseEntityBundle.fields|{}fields[0]
diff --git a/settings.gradle b/settings.gradle
index d0f669c..ded0605 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -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])
@@ -597,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])
@@ -655,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])
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/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-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 3b402a4..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,6 +320,45 @@
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;
@@ -401,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;
@@ -422,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);
}
@@ -432,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();
@@ -548,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();
@@ -1119,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;
@@ -1134,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 {
@@ -1151,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();
@@ -1221,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();
}
@@ -1296,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 3b402a4..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,6 +320,45 @@
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;
@@ -401,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;
@@ -422,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);
}
@@ -432,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();
@@ -548,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();
@@ -1119,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;
@@ -1134,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 {
@@ -1151,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();
@@ -1221,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();
}
@@ -1296,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/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/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/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index 082ed2d..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() } },
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/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/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/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/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 e6e84b1..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,6 +254,14 @@
// 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/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/IconButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
index 7f89966..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
@@ -355,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
@@ -372,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,
@@ -606,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
@@ -616,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
@@ -649,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,
@@ -797,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),
@@ -914,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/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/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/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/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
index d19c450..4951307 100644
--- a/wear/compose/compose-material3/src/main/res/values-af/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-af/strings.xml
@@ -33,5 +33,13 @@
<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
index ee40287..da5ab9c 100644
--- a/wear/compose/compose-material3/src/main/res/values-am/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-am/strings.xml
@@ -33,5 +33,13 @@
<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
index 6f65d6e..450227f 100644
--- a/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
@@ -45,5 +45,13 @@
<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
index 867bba0..178eaa4 100644
--- a/wear/compose/compose-material3/src/main/res/values-as/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-as/strings.xml
@@ -33,5 +33,13 @@
<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
index 43fc687..de43d4f 100644
--- a/wear/compose/compose-material3/src/main/res/values-az/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-az/strings.xml
@@ -33,5 +33,13 @@
<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
index 7130d77..eca919a 100644
--- 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
@@ -36,5 +36,13 @@
<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
index 1425da3..dc5e832 100644
--- a/wear/compose/compose-material3/src/main/res/values-be/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-be/strings.xml
@@ -39,5 +39,13 @@
<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
index a2b7c61..2866f08 100644
--- a/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
@@ -33,5 +33,9 @@
<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
index b68d6d9..34a09f1 100644
--- a/wear/compose/compose-material3/src/main/res/values-bn/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-bn/strings.xml
@@ -33,5 +33,13 @@
<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
index 0654f2f..9e53fc8 100644
--- a/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
@@ -36,5 +36,13 @@
<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
index cd62e3f..0c19945 100644
--- a/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
@@ -36,5 +36,9 @@
<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
index e8dfcdc..98dc3ea 100644
--- a/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
@@ -39,5 +39,13 @@
<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
index 340890b..d1bd436 100644
--- a/wear/compose/compose-material3/src/main/res/values-da/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-da/strings.xml
@@ -33,5 +33,13 @@
<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
index fd7b287..9fdd438 100644
--- a/wear/compose/compose-material3/src/main/res/values-de/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-de/strings.xml
@@ -33,5 +33,13 @@
<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
index b105e52..20374c9 100644
--- a/wear/compose/compose-material3/src/main/res/values-el/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-el/strings.xml
@@ -33,5 +33,13 @@
<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
index b52363a..58982b2 100644
--- 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
@@ -33,5 +33,13 @@
<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
index 37d75e2..fb5c42c 100644
--- 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
@@ -33,5 +33,9 @@
<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
index b52363a..58982b2 100644
--- 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
@@ -33,5 +33,13 @@
<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
index b52363a..58982b2 100644
--- 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
@@ -33,5 +33,13 @@
<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
index ba2dc8b..0bff2a4 100644
--- 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
@@ -33,5 +33,9 @@
<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
index e2ae406..898d4f1 100644
--- a/wear/compose/compose-material3/src/main/res/values-et/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-et/strings.xml
@@ -33,5 +33,13 @@
<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
index 98f7e97..202e7ee 100644
--- a/wear/compose/compose-material3/src/main/res/values-eu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-eu/strings.xml
@@ -33,5 +33,13 @@
<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
index e51ff20..8f9c137 100644
--- a/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
@@ -33,5 +33,13 @@
<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
index 7a0c590..0f0b50b 100644
--- a/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
@@ -33,5 +33,13 @@
<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
index e1e9794..cdc3ad5 100644
--- 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
@@ -36,5 +36,13 @@
<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
index 97370e0..bdb1f1b 100644
--- a/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
@@ -36,5 +36,13 @@
<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
index 1f5b0d0..ad5ca33 100644
--- a/wear/compose/compose-material3/src/main/res/values-gl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-gl/strings.xml
@@ -33,5 +33,13 @@
<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
index cd43c67..b01e9be 100644
--- a/wear/compose/compose-material3/src/main/res/values-gu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-gu/strings.xml
@@ -33,5 +33,13 @@
<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
index 0d50a66..c861b3af 100644
--- a/wear/compose/compose-material3/src/main/res/values-hi/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hi/strings.xml
@@ -33,5 +33,9 @@
<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
index 71b0d59..af212a5 100644
--- a/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
@@ -36,5 +36,13 @@
<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
index ead4509..2cce16e 100644
--- a/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
@@ -33,5 +33,13 @@
<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
index bfff0c2..cda99db 100644
--- a/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
@@ -33,5 +33,13 @@
<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
index aba3a73..66a973e 100644
--- a/wear/compose/compose-material3/src/main/res/values-in/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-in/strings.xml
@@ -33,5 +33,13 @@
<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
index 1065161..8983c3f 100644
--- a/wear/compose/compose-material3/src/main/res/values-is/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-is/strings.xml
@@ -33,5 +33,13 @@
<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
index 33d9d41..a9566b7 100644
--- a/wear/compose/compose-material3/src/main/res/values-it/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-it/strings.xml
@@ -36,5 +36,13 @@
<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
index 320aeef..91f1e27 100644
--- a/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
@@ -36,5 +36,13 @@
<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
index 2b82492..65e9cc7 100644
--- a/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
@@ -33,5 +33,9 @@
<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
index f1930fd..4f4734e 100644
--- a/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
@@ -33,5 +33,9 @@
<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
index 0b79463..e0d8b3d 100644
--- a/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
@@ -33,5 +33,13 @@
<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
index b302b3b..410a6f2 100644
--- a/wear/compose/compose-material3/src/main/res/values-km/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-km/strings.xml
@@ -33,5 +33,13 @@
<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
index fe7c9cf..6ae6994 100644
--- a/wear/compose/compose-material3/src/main/res/values-kn/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-kn/strings.xml
@@ -33,5 +33,13 @@
<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
index 231ff8f5..45f8446 100644
--- a/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
@@ -33,5 +33,13 @@
<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
index efbade7..692726e 100644
--- a/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
@@ -33,5 +33,13 @@
<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
index 8e4a64e..59d2d94 100644
--- a/wear/compose/compose-material3/src/main/res/values-lo/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-lo/strings.xml
@@ -33,5 +33,13 @@
<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
index ad7f022..615054e 100644
--- a/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
@@ -39,5 +39,13 @@
<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
index 1057111..08f2d2c 100644
--- a/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
@@ -36,5 +36,13 @@
<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
index 3e57b97..a093425 100644
--- a/wear/compose/compose-material3/src/main/res/values-mk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-mk/strings.xml
@@ -33,5 +33,13 @@
<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
index ff5642f..36d8b54 100644
--- a/wear/compose/compose-material3/src/main/res/values-ml/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ml/strings.xml
@@ -33,5 +33,13 @@
<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
index 7db8e1c..629e16a 100644
--- a/wear/compose/compose-material3/src/main/res/values-mn/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-mn/strings.xml
@@ -33,5 +33,13 @@
<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
index 23e9efe..904ad4f 100644
--- a/wear/compose/compose-material3/src/main/res/values-mr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-mr/strings.xml
@@ -33,5 +33,13 @@
<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
index 5869290..df4b8c9 100644
--- a/wear/compose/compose-material3/src/main/res/values-ms/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ms/strings.xml
@@ -33,5 +33,13 @@
<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
index f328281..aa85494 100644
--- a/wear/compose/compose-material3/src/main/res/values-my/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-my/strings.xml
@@ -33,5 +33,13 @@
<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
index b4fd2e7..0d9dbd3 100644
--- a/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
@@ -33,5 +33,13 @@
<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
index b234e16..4f5d479 100644
--- a/wear/compose/compose-material3/src/main/res/values-ne/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ne/strings.xml
@@ -33,5 +33,13 @@
<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
index 3afe2c9..8561545 100644
--- a/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
@@ -33,5 +33,13 @@
<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
index acf7af8..7701b598 100644
--- a/wear/compose/compose-material3/src/main/res/values-or/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-or/strings.xml
@@ -33,5 +33,13 @@
<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
index 80dbbc1..1000cde 100644
--- a/wear/compose/compose-material3/src/main/res/values-pa/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pa/strings.xml
@@ -33,5 +33,13 @@
<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
index 84a021b..5d56d82 100644
--- a/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
@@ -39,5 +39,13 @@
<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
index 2dcdcbf..77c3710 100644
--- 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
@@ -36,5 +36,13 @@
<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
index 9bbdb89..416cbc0 100644
--- 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
@@ -36,5 +36,9 @@
<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
index 2dcdcbf..77c3710 100644
--- a/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
@@ -36,5 +36,13 @@
<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
index a682983b..f9723df 100644
--- a/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
@@ -36,5 +36,13 @@
<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
index 47fb6f8..e01e3c3 100644
--- a/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
@@ -39,5 +39,13 @@
<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
index 491d592..6f3d4c6 100644
--- a/wear/compose/compose-material3/src/main/res/values-si/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-si/strings.xml
@@ -33,5 +33,13 @@
<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
index 7b45b82..8840530 100644
--- a/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
@@ -39,5 +39,13 @@
<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
index 164b53e..cc79d76 100644
--- a/wear/compose/compose-material3/src/main/res/values-sl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sl/strings.xml
@@ -39,5 +39,9 @@
<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
index 149c7bb..411d53e 100644
--- a/wear/compose/compose-material3/src/main/res/values-sq/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sq/strings.xml
@@ -33,5 +33,13 @@
<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
index decaa24c..661b6d2 100644
--- a/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
@@ -36,5 +36,13 @@
<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
index 33c96f7..b395ba1d 100644
--- a/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
@@ -33,5 +33,13 @@
<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
index d1cc257..aad1959 100644
--- a/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
@@ -33,5 +33,13 @@
<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
index 669314b..aacd3cd 100644
--- a/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
@@ -33,5 +33,13 @@
<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
index 353d9fc..2374063 100644
--- a/wear/compose/compose-material3/src/main/res/values-te/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-te/strings.xml
@@ -33,5 +33,9 @@
<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
index 3bd9b5c..d1ad793 100644
--- a/wear/compose/compose-material3/src/main/res/values-th/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-th/strings.xml
@@ -33,5 +33,9 @@
<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
index 5d1f051..fe20f09 100644
--- a/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
@@ -33,5 +33,9 @@
<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
index 1a0f669..3ec35be 100644
--- a/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
@@ -33,5 +33,13 @@
<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
index 40e732c..bb037ea 100644
--- a/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
@@ -39,5 +39,13 @@
<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
index f15dacf..6f902ab 100644
--- a/wear/compose/compose-material3/src/main/res/values-ur/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ur/strings.xml
@@ -33,5 +33,13 @@
<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
index 1025cad..4b1121e 100644
--- a/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
@@ -33,5 +33,9 @@
<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
index 6d61d00..3b40f28 100644
--- a/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
@@ -33,5 +33,13 @@
<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
index d89158c..14ce25a 100644
--- 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
@@ -33,5 +33,9 @@
<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
index 68bc1b0..0d30e02 100644
--- 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
@@ -33,5 +33,13 @@
<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
index 067f1dc..3e569a9 100644
--- 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
@@ -33,5 +33,13 @@
<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
index 159343b..43167ed 100644
--- a/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
@@ -33,5 +33,13 @@
<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 213c3ad..868ca2b 100644
--- a/wear/compose/compose-material3/src/main/res/values/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values/strings.xml
@@ -36,4 +36,8 @@
<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>
<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>
-</resources>
\ No newline at end of file
+
+ <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/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/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) }) }
}
/**